diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c20c2e9..94d9ae4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,19 +1,16 @@ plugins { - androidApplication + androidLibrary + kotlinAndroid } -val packageName = "ru.myitschool.work" +val packageName = "ru.innovationcampus.test-lib" android { namespace = packageName compileSdk = Version.Android.Sdk.compile defaultConfig { - applicationId = packageName minSdk = Version.Android.Sdk.min - targetSdk = Version.Android.Sdk.target - versionCode = 1 - versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -25,5 +22,10 @@ android { } dependencies { - defaultLibrary() + api(Dependencies.Kaspresso.core) + api(Dependencies.Kaspresso.composeSupport) + api(Dependencies.AndroidX.Testing.junit) + api(Dependencies.AndroidX.Testing.compose) + api(Dependencies.AndroidX.Testing.orchestrator) + api(Dependencies.truth) } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index 481bb43..0000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml deleted file mode 100644 index 9ee5c40..0000000 --- a/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/java/ru/innovationcampus/test/core/core/BaseTest.kt b/app/src/main/java/ru/innovationcampus/test/core/core/BaseTest.kt new file mode 100644 index 0000000..247d668 --- /dev/null +++ b/app/src/main/java/ru/innovationcampus/test/core/core/BaseTest.kt @@ -0,0 +1,83 @@ +package ru.innovationcampus.test.core.core + +import android.app.Activity +import android.app.Instrumentation +import android.content.Context +import android.content.Intent +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.core.app.ActivityScenario +import androidx.test.espresso.Espresso +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.kaspersky.components.composesupport.config.addComposeSupport +import com.kaspersky.kaspresso.idlewaiting.KautomatorWaitForIdleSettings +import com.kaspersky.kaspresso.kaspresso.Kaspresso +import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import com.kaspersky.kaspresso.testcases.core.testcontext.TestContext +import java.util.Locale +import org.junit.After +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Rule +import org.junit.runners.MethodSorters +import ru.innovationcampus.test.core.utils.ResultTestsData + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class BaseTest( + private val clazz: Class +) : TestCase(kaspressoBuilder = Kaspresso.Builder.simple { + kautomatorWaitForIdleSettings = KautomatorWaitForIdleSettings.boost() +}.apply { addComposeSupport() }) { + @get:Rule + val composeTestRule = createComposeRule() + + protected lateinit var activityScenario: ActivityScenario + private set + protected lateinit var uiDevice: UiDevice + private set + protected lateinit var appContext: Context + private set + protected lateinit var instrumentation: Instrumentation + private set + + open fun beforeTest() {} + + @Before + fun setUp() { + instrumentation = InstrumentationRegistry.getInstrumentation() + val handler = DescriptionFailureHandler(instrumentation) + Espresso.setFailureHandler(handler) + + uiDevice = UiDevice.getInstance(instrumentation) + uiDevice.pressHome() + Thread.sleep(1_000) + + val nonLocalizedContext = instrumentation.targetContext + val configuration = nonLocalizedContext.resources.configuration + configuration.setLocale(Locale.UK) + appContext = nonLocalizedContext.createConfigurationContext(configuration) + + val intent = Intent(appContext, clazz) + + activityScenario = ActivityScenario.launch(intent) + + beforeTest() + } + + @After + fun reset() { + uiDevice.pressHome() + } + + fun runWithInit(grade: Int, test: TestContext.() -> Unit) = run { + ResultTestsData.setupTest(grade) + try { + test.invoke(this) + ResultTestsData.successTest(grade) + } catch (e: Exception) { + ResultTestsData.putResult(instrumentation, uiDevice) + throw AssertionError(e.message) + } + ResultTestsData.putResult(instrumentation, uiDevice) + } +} diff --git a/app/src/main/java/ru/innovationcampus/test/core/core/DescriptionFailureHandler.kt b/app/src/main/java/ru/innovationcampus/test/core/core/DescriptionFailureHandler.kt new file mode 100644 index 0000000..3cc070c --- /dev/null +++ b/app/src/main/java/ru/innovationcampus/test/core/core/DescriptionFailureHandler.kt @@ -0,0 +1,31 @@ +package ru.innovationcampus.test.core.core + +import android.app.Instrumentation +import android.view.View +import androidx.test.espresso.FailureHandler +import androidx.test.espresso.base.DefaultFailureHandler +import org.hamcrest.Matcher +import kotlin.math.min + +class DescriptionFailureHandler(instrumentation: Instrumentation) : FailureHandler { + var extraMessage = "" + private var delegate: DefaultFailureHandler = + DefaultFailureHandler(instrumentation.targetContext) + + override fun handle(error: Throwable?, viewMatcher: Matcher?) { + // Log anything you want here + if (error != null) { + val newError = Throwable( + + "$extraMessage " + error.message?.substring( + 0, min( + 1_700, error.message?.length ?: 0 + ) + ) + "...", error.cause + ) + + // Then delegate the error handling to the default handler which will throw an exception + delegate.handle(newError, viewMatcher) + } + } +} diff --git a/app/src/main/java/ru/innovationcampus/test/core/utils/ResultTestsData.kt b/app/src/main/java/ru/innovationcampus/test/core/utils/ResultTestsData.kt new file mode 100644 index 0000000..25b45df --- /dev/null +++ b/app/src/main/java/ru/innovationcampus/test/core/utils/ResultTestsData.kt @@ -0,0 +1,48 @@ +package ru.innovationcampus.test.core.utils + +import android.app.Activity +import android.app.Instrumentation +import android.app.Instrumentation.ActivityResult +import android.os.Build +import android.os.Bundle +import android.util.Log +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice + +object ResultTestsData { + private var grade = 0 + private var totalTests = 0 + private var maxGrade = 0 + private var passTests = 0 + + fun setupTest(grade: Int) { + totalTests++ + maxGrade += grade + } + + fun successTest(grade: Int) { + passTests++ + ResultTestsData.grade += grade + } + + + fun putResult( + instrumentation: Instrumentation, + uiDevice: UiDevice + ) { + uiDevice.pressHome() + + val results = Bundle() + results.putInt("passTests", passTests) + results.putInt("totalTests", totalTests) + results.putInt("grade", grade) + results.putInt("maxGrade", maxGrade) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + instrumentation.addResults(results) + } else { + instrumentation.sendStatus(Activity.RESULT_OK, results) + } + Log.d("Tests", "$passTests из $totalTests тестов пройдено.") + Log.d("Tests", "$grade из $maxGrade баллов получено.") + } +} diff --git a/app/src/main/java/ru/innovationcampus/test/core/utils/UiTextViewExtensions.kt b/app/src/main/java/ru/innovationcampus/test/core/utils/UiTextViewExtensions.kt new file mode 100644 index 0000000..8cc4f86 --- /dev/null +++ b/app/src/main/java/ru/innovationcampus/test/core/utils/UiTextViewExtensions.kt @@ -0,0 +1,17 @@ +package ru.innovationcampus.test.core.utils + +import com.google.common.truth.Truth.assertThat +import com.kaspersky.components.kautomator.component.text.UiTextViewAssertions +import com.kaspersky.components.kautomator.intercept.operation.UiOperationType + +fun UiTextViewAssertions.checkRegexp(regexp: String) { + view.check( + object : UiOperationType { + override val name = "Check text by regexp" + }, + description = "" + ) { + assertThat(text).isNotEmpty() + assertThat(text).matches(regexp) + } +} diff --git a/app/src/main/java/ru/innovationcampus/test/core/utils/UiViewBuilderExtensions.kt b/app/src/main/java/ru/innovationcampus/test/core/utils/UiViewBuilderExtensions.kt new file mode 100644 index 0000000..7754a54 --- /dev/null +++ b/app/src/main/java/ru/innovationcampus/test/core/utils/UiViewBuilderExtensions.kt @@ -0,0 +1,10 @@ +package ru.innovationcampus.test.core.utils + +import com.kaspersky.components.kautomator.component.common.builders.UiViewBuilder + +/** + * Используем для поиска элемента по id для compose и обычной view + * + * @param resId универсальный идентификатор + */ +fun UiViewBuilder.withRes(resId: String) = withSelector { res(resId) } diff --git a/app/src/main/java/ru/innovationcampus/test/core/utils/Utils.kt b/app/src/main/java/ru/innovationcampus/test/core/utils/Utils.kt new file mode 100644 index 0000000..af54c41 --- /dev/null +++ b/app/src/main/java/ru/innovationcampus/test/core/utils/Utils.kt @@ -0,0 +1,24 @@ +package ru.innovationcampus.test.core.utils + +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import io.github.kakaocup.compose.node.element.ComposeScreen +import io.github.kakaocup.compose.node.element.KNode + +/** + * Функцию необходимо вызывать для прогрузки Compose экрана + * Перед каждым открытием экрана вызывайте эту функцию, иначе экраны, + * написанные на Compose могут не прогрузиться. + */ +fun warmUpCompose( + semanticsProvider: SemanticsNodeInteractionsProvider +) = BlankScreen(semanticsProvider).waitLoadingScreen.assertDoesNotExist() + +private class BlankScreen( + semanticsProvider: SemanticsNodeInteractionsProvider +) : ComposeScreen( + semanticsProvider = semanticsProvider, + viewBuilderAction = { hasTestTag("InvalidTag") } +) { + val waitLoadingScreen: KNode + get() = child { hasTestTag("InvalidTag") } +} diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9..0000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d1..0000000 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 6f3b755..0000000 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 6f3b755..0000000 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index c209e78..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index b2dfe3d..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d6..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 62b611d..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 948a307..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index 1b9a695..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 28d4b77..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9287f50..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index aa7d642..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9126ae3..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml deleted file mode 100644 index f8c6127..0000000 --- a/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index 96034ac..0000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Work - \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml deleted file mode 100644 index 89e63d4..0000000 --- a/app/src/main/res/values/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml deleted file mode 100644 index fa0f996..0000000 --- a/app/src/main/res/xml/backup_rules.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml deleted file mode 100644 index 9ee9997..0000000 --- a/app/src/main/res/xml/data_extraction_rules.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 860f2c8..074c663 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - androidApplication version Version.gradle apply false - kotlinJvm version Version.Kotlin.language apply false -} \ No newline at end of file + androidLibrary version Version.gradle apply false + kotlinAndroid version Version.Kotlin.language apply false +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 4da3ae4..74b7a13 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,5 +13,5 @@ dependencyResolutionManagement { } } -rootProject.name = "Work" +rootProject.name = "TestCore" include(":app")