From 24388192e12a8143a1e5d5e00505c910981a5e73 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Tue, 6 Dec 2022 20:16:48 +0100 Subject: [PATCH 01/15] Add first Kaspresso test --- app/build.gradle.kts | 10 ++++++ app/src/androidTest/AndroidManifest.xml | 4 +++ .../gq/kirmanak/mealient/FirstSetUpTest.kt | 32 +++++++++++++++++++ .../kirmanak/mealient/MealientTestRunner.kt | 15 +++++++++ .../mealient/screen/DisclaimerScreen.kt | 13 ++++++++ gradle/libs.versions.toml | 10 ++++++ 6 files changed, 84 insertions(+) create mode 100644 app/src/androidTest/AndroidManifest.xml create mode 100644 app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt create mode 100644 app/src/androidTest/kotlin/gq/kirmanak/mealient/MealientTestRunner.kt create mode 100644 app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/DisclaimerScreen.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0ac419b..ea97c39 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,6 +17,7 @@ android { applicationId = "gq.kirmanak.mealient" versionCode = 25 versionName = "0.3.10" + testInstrumentationRunner = "gq.kirmanak.mealient.MealientTestRunner" } signingConfigs { @@ -89,6 +90,8 @@ dependencies { kapt(libs.google.dagger.hiltCompiler) kaptTest(libs.google.dagger.hiltAndroidCompiler) testImplementation(libs.google.dagger.hiltAndroidTesting) + kaptAndroidTest(libs.google.dagger.hiltAndroidCompiler) + androidTestImplementation(libs.google.dagger.hiltAndroidTesting) implementation(libs.androidx.paging.runtimeKtx) testImplementation(libs.androidx.paging.commonKtx) @@ -122,4 +125,11 @@ dependencies { testImplementation(libs.io.mockk) debugImplementation(libs.squareup.leakcanary) + + androidTestImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.junit) + androidTestImplementation(libs.kaspersky.kaspresso) + androidTestImplementation(libs.androidx.test.core) + androidTestImplementation(libs.androidx.test.rules) + androidTestImplementation(libs.androidx.test.runner) } \ No newline at end of file diff --git a/app/src/androidTest/AndroidManifest.xml b/app/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000..cfb2be6 --- /dev/null +++ b/app/src/androidTest/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt b/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt new file mode 100644 index 0000000..85581fe --- /dev/null +++ b/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt @@ -0,0 +1,32 @@ +package gq.kirmanak.mealient + +import androidx.test.ext.junit.rules.activityScenarioRule +import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import gq.kirmanak.mealient.screen.DisclaimerScreen +import gq.kirmanak.mealient.ui.activity.MainActivity +import org.junit.Rule +import org.junit.Test + +@HiltAndroidTest +class FirstSetUpTest : TestCase() { + + @get:Rule(order = 0) + var hiltRule = HiltAndroidRule(this) + + @get:Rule(order = 1) + val mainActivityRule = activityScenarioRule() + + @Test + fun test() = run { + step("Ensure button is disabled") { + DisclaimerScreen { + okayButton { + isVisible() + isDisabled() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/gq/kirmanak/mealient/MealientTestRunner.kt b/app/src/androidTest/kotlin/gq/kirmanak/mealient/MealientTestRunner.kt new file mode 100644 index 0000000..4352422 --- /dev/null +++ b/app/src/androidTest/kotlin/gq/kirmanak/mealient/MealientTestRunner.kt @@ -0,0 +1,15 @@ +package gq.kirmanak.mealient + +import android.app.Application +import android.content.Context +import androidx.test.runner.AndroidJUnitRunner +import dagger.hilt.android.testing.HiltTestApplication + +class MealientTestRunner : AndroidJUnitRunner() { + + override fun newApplication( + cl: ClassLoader?, + className: String?, + context: Context?, + ): Application = super.newApplication(cl, HiltTestApplication::class.java.name, context) +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/DisclaimerScreen.kt b/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/DisclaimerScreen.kt new file mode 100644 index 0000000..d06ab45 --- /dev/null +++ b/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/DisclaimerScreen.kt @@ -0,0 +1,13 @@ +package gq.kirmanak.mealient.screen + +import com.kaspersky.kaspresso.screens.KScreen +import gq.kirmanak.mealient.R +import gq.kirmanak.mealient.ui.activity.MainActivity +import io.github.kakaocup.kakao.text.KButton + +object DisclaimerScreen : KScreen() { + override val layoutId = R.layout.fragment_disclaimer + override val viewClass = MainActivity::class.java + + val okayButton = KButton { withId(R.id.okay) } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f6f528f..b7bee3d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -77,6 +77,10 @@ desugar = "1.2.2" kspPlugin = "1.7.20-1.0.7" # https://developer.android.com/jetpack/androidx/releases/sharetarget shareTarget = "1.2.0" +# https://github.com/KasperskyLab/Kaspresso/releases +kaspresso = "1.4.2" +# https://developer.android.com/jetpack/androidx/releases/test +androidXTest = "1.5.0" [libraries] android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } @@ -133,6 +137,10 @@ androidx-room-testing = { group = "androidx.room", name = "room-testing", versio androidx-test-junit = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "junitKtx" } androidx-security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "security" } +androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidXTest" } +androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidXTest" } +androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidXTest" } + jakewharton-retrofitSerialization = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinxSerialization" } squareup-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } @@ -158,6 +166,8 @@ io-mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } chuckerteam-chucker = { group = "com.github.chuckerteam.chucker", name = "library", version.ref = "chucker" } +kaspersky-kaspresso = { group = "com.kaspersky.android-components", name = "kaspresso", version.ref = "kaspresso" } + [plugins] sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" } rootcoverage = { id = "nl.neotech.plugin.rootcoverage", version.ref = "rootCoverage" } From 8c3ec9a40d7e501282912854665bcc5b4b7fbe10 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Tue, 6 Dec 2022 20:32:28 +0100 Subject: [PATCH 02/15] Enable AndroidX test orchestrator --- app/build.gradle.kts | 6 ++++++ gradle/libs.versions.toml | 3 +++ 2 files changed, 9 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ea97c39..602fbc3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -18,6 +18,7 @@ android { versionCode = 25 versionName = "0.3.10" testInstrumentationRunner = "gq.kirmanak.mealient.MealientTestRunner" + testInstrumentationRunnerArguments += mapOf("clearPackageData" to "true") } signingConfigs { @@ -55,6 +56,10 @@ android { packagingOptions { resources.excludes += "DebugProbesKt.bin" } + + testOptions { + execution = "ANDROIDX_TEST_ORCHESTRATOR" + } } dependencies { @@ -132,4 +137,5 @@ dependencies { androidTestImplementation(libs.androidx.test.core) androidTestImplementation(libs.androidx.test.rules) androidTestImplementation(libs.androidx.test.runner) + androidTestUtil(libs.androidx.test.orchestrator) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b7bee3d..61e469e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -81,6 +81,8 @@ shareTarget = "1.2.0" kaspresso = "1.4.2" # https://developer.android.com/jetpack/androidx/releases/test androidXTest = "1.5.0" +# https://developer.android.com/jetpack/androidx/releases/test +androidXTestOrchestrator = "1.4.2" [libraries] android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } @@ -140,6 +142,7 @@ androidx-security-crypto = { group = "androidx.security", name = "security-crypt androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidXTest" } androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidXTest" } androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidXTest" } +androidx-test-orchestrator = { group = "androidx.test", name = "orchestrator", version.ref = "androidXTestOrchestrator" } jakewharton-retrofitSerialization = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinxSerialization" } From 77395a07f445488be866dc5f20c6517ded653f05 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Tue, 6 Dec 2022 21:33:10 +0100 Subject: [PATCH 03/15] Add base url setup to sign in test --- app/build.gradle.kts | 1 + .../gq/kirmanak/mealient/FirstSetUpTest.kt | 60 ++++++++++++++++++- .../mealient/response.VersionResponses.kt | 20 +++++++ .../kirmanak/mealient/screen/BaseUrlScreen.kt | 19 ++++++ .../mealient/screen/DisclaimerScreen.kt | 7 ++- .../mealient/screen/RecipesListScreen.kt | 13 ++++ .../mealient/ui/baseurl/BaseURLViewModel.kt | 3 +- gradle/libs.versions.toml | 1 + 8 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 app/src/androidTest/kotlin/gq/kirmanak/mealient/response.VersionResponses.kt create mode 100644 app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/BaseUrlScreen.kt create mode 100644 app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/RecipesListScreen.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 602fbc3..2633cea 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -134,6 +134,7 @@ dependencies { androidTestImplementation(libs.junit) androidTestImplementation(libs.androidx.test.junit) androidTestImplementation(libs.kaspersky.kaspresso) + androidTestImplementation(libs.okhttp3.mockwebserver) androidTestImplementation(libs.androidx.test.core) androidTestImplementation(libs.androidx.test.rules) androidTestImplementation(libs.androidx.test.runner) diff --git a/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt b/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt index 85581fe..ff3d93e 100644 --- a/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt +++ b/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt @@ -4,8 +4,11 @@ import androidx.test.ext.junit.rules.activityScenarioRule import com.kaspersky.kaspresso.testcases.api.testcase.TestCase import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest +import gq.kirmanak.mealient.screen.BaseUrlScreen import gq.kirmanak.mealient.screen.DisclaimerScreen +import gq.kirmanak.mealient.screen.RecipesListScreen import gq.kirmanak.mealient.ui.activity.MainActivity +import okhttp3.mockwebserver.MockWebServer import org.junit.Rule import org.junit.Test @@ -18,13 +21,68 @@ class FirstSetUpTest : TestCase() { @get:Rule(order = 1) val mainActivityRule = activityScenarioRule() + private lateinit var mockWebServer: MockWebServer + @Test - fun test() = run { + fun test() = before { + mockWebServer = MockWebServer() + mockWebServer.dispatch { url, _ -> + if (url == "/api/app/about") versionV1Response else notFoundResponse + } + mockWebServer.start() + }.after { + mockWebServer.shutdown() + }.run { step("Ensure button is disabled") { DisclaimerScreen { okayButton { isVisible() isDisabled() + hasAnyText() + } + + disclaimerText { + isVisible() + hasText(R.string.fragment_disclaimer_main_text) + } + } + } + + step("Close disclaimer screen") { + DisclaimerScreen { + okayButton { + isVisible() + isEnabled() + hasText(R.string.fragment_disclaimer_button_okay) + click() + } + } + } + + step("Enter mock server address and click proceed") { + BaseUrlScreen { + progressBar { + isGone() + } + urlInput { + isVisible() + edit.replaceText(mockWebServer.url("/").toString()) + hasHint(R.string.fragment_authentication_input_hint_url) + } + proceedButton { + isVisible() + isEnabled() + hasText(R.string.fragment_base_url_save) + click() + } + } + } + + step("Check that empty list of recipes is shown") { + RecipesListScreen { + emptyListText { + isVisible() + hasText(R.string.fragment_recipes_list_no_recipes) } } } diff --git a/app/src/androidTest/kotlin/gq/kirmanak/mealient/response.VersionResponses.kt b/app/src/androidTest/kotlin/gq/kirmanak/mealient/response.VersionResponses.kt new file mode 100644 index 0000000..51f8b14 --- /dev/null +++ b/app/src/androidTest/kotlin/gq/kirmanak/mealient/response.VersionResponses.kt @@ -0,0 +1,20 @@ +package gq.kirmanak.mealient + +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest + +val versionV1Response = MockResponse().setResponseCode(200).setBody( + """{"production":true,"version":"v1.0.0beta-5","demoStatus":false,"allowSignup":true}""" +) + +val notFoundResponse = MockResponse().setResponseCode(404).setBody("""{"detail":"Not found"}"""") + +fun MockWebServer.dispatch(block: (String, RecordedRequest) -> MockResponse) { + dispatcher = object : Dispatcher() { + override fun dispatch(request: RecordedRequest): MockResponse { + return block(request.path.orEmpty(), request) + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/BaseUrlScreen.kt b/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/BaseUrlScreen.kt new file mode 100644 index 0000000..00f549f --- /dev/null +++ b/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/BaseUrlScreen.kt @@ -0,0 +1,19 @@ +package gq.kirmanak.mealient.screen + +import com.kaspersky.kaspresso.screens.KScreen +import gq.kirmanak.mealient.R +import gq.kirmanak.mealient.ui.baseurl.BaseURLFragment +import io.github.kakaocup.kakao.edit.KTextInputLayout +import io.github.kakaocup.kakao.progress.KProgressBar +import io.github.kakaocup.kakao.text.KButton + +object BaseUrlScreen : KScreen() { + override val layoutId = R.layout.fragment_base_url + override val viewClass = BaseURLFragment::class.java + + val urlInput = KTextInputLayout { withId(R.id.url_input_layout) } + + val proceedButton = KButton { withId(R.id.button) } + + val progressBar = KProgressBar { withId(R.id.progress)} +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/DisclaimerScreen.kt b/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/DisclaimerScreen.kt index d06ab45..5dafb30 100644 --- a/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/DisclaimerScreen.kt +++ b/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/DisclaimerScreen.kt @@ -2,12 +2,15 @@ package gq.kirmanak.mealient.screen import com.kaspersky.kaspresso.screens.KScreen import gq.kirmanak.mealient.R -import gq.kirmanak.mealient.ui.activity.MainActivity +import gq.kirmanak.mealient.ui.disclaimer.DisclaimerFragment import io.github.kakaocup.kakao.text.KButton +import io.github.kakaocup.kakao.text.KTextView object DisclaimerScreen : KScreen() { override val layoutId = R.layout.fragment_disclaimer - override val viewClass = MainActivity::class.java + override val viewClass = DisclaimerFragment::class.java val okayButton = KButton { withId(R.id.okay) } + + val disclaimerText = KTextView { withId(R.id.main_text) } } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/RecipesListScreen.kt b/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/RecipesListScreen.kt new file mode 100644 index 0000000..6c2b171 --- /dev/null +++ b/app/src/androidTest/kotlin/gq/kirmanak/mealient/screen/RecipesListScreen.kt @@ -0,0 +1,13 @@ +package gq.kirmanak.mealient.screen + +import com.kaspersky.kaspresso.screens.KScreen +import gq.kirmanak.mealient.R +import gq.kirmanak.mealient.ui.recipes.RecipesListFragment +import io.github.kakaocup.kakao.text.KTextView + +object RecipesListScreen : KScreen() { + override val layoutId: Int = R.layout.fragment_recipes_list + override val viewClass: Class<*> = RecipesListFragment::class.java + + val emptyListText = KTextView { withId(R.id.empty_list_text) } +} \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt index 6330c7a..42afc2b 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt @@ -28,7 +28,8 @@ class BaseURLViewModel @Inject constructor( logger.v { "saveBaseUrl() called with: baseURL = $baseURL" } _uiState.value = OperationUiState.Progress() val hasPrefix = ALLOWED_PREFIXES.any { baseURL.startsWith(it) } - val url = baseURL.takeIf { hasPrefix } ?: WITH_PREFIX_FORMAT.format(baseURL) + var url = baseURL.takeIf { hasPrefix } ?: WITH_PREFIX_FORMAT.format(baseURL) + url = url.trimStart().trimEnd { it == '/' || it.isWhitespace() } viewModelScope.launch { checkBaseURL(url) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 61e469e..cacae3b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -153,6 +153,7 @@ squareup-leakcanary = { group = "com.squareup.leakcanary", name = "leakcanary-an okhttp3-bom = { group = "com.squareup.okhttp3", name = "okhttp-bom", version.ref = "okhttp" } okhttp3-okhttp = { group = "com.squareup.okhttp3", name = "okhttp" } okhttp3-loggingInterceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor" } +okhttp3-mockwebserver = { group = "com.squareup.okhttp3", name = "mockwebserver", version.ref = "okhttp" } bumptech-glide-glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" } bumptech-glide-okhttp3 = { group = "com.github.bumptech.glide", name = "okhttp3-integration", version.ref = "glide" } From caaebfd1270d5a79a60e089445e0eea31131997c Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Thu, 8 Dec 2022 19:25:22 +0100 Subject: [PATCH 04/15] Remove redundant / in test data --- app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt b/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt index cc8bb47..640a559 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt @@ -7,7 +7,7 @@ import gq.kirmanak.mealient.datasource.v1.models.GetUserInfoResponseV1 object AuthImplTestData { const val TEST_USERNAME = "TEST_USERNAME" const val TEST_PASSWORD = "TEST_PASSWORD" - const val TEST_BASE_URL = "https://example.com/" + const val TEST_BASE_URL = "https://example.com" const val TEST_TOKEN = "TEST_TOKEN" const val TEST_AUTH_HEADER = "Bearer TEST_TOKEN" const val TEST_API_TOKEN = "TEST_API_TOKEN" From 0ebcc8709c2369d86287ddcdcf571b8626226050 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Fri, 16 Dec 2022 21:50:12 +0100 Subject: [PATCH 05/15] Add Gradle managed virtual device --- app/build.gradle.kts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2633cea..273a884 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,6 @@ @file:Suppress("UnstableApiUsage") +import com.android.build.api.dsl.ManagedVirtualDevice import java.io.FileInputStream import java.util.* @@ -60,6 +61,18 @@ android { testOptions { execution = "ANDROIDX_TEST_ORCHESTRATOR" } + + testOptions { + managedDevices { + devices { + maybeCreate("pixel2api30").apply { + device = "Pixel 2" + apiLevel = 30 + systemImageSource = "aosp-atd" + } + } + } + } } dependencies { From e16dee3ee526e05dfd5c3f65e0b10b8df908915c Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Fri, 16 Dec 2022 22:10:03 +0100 Subject: [PATCH 06/15] Extract base test case --- .../gq/kirmanak/mealient/BaseTestCase.kt | 33 +++++++++++++++++++ .../gq/kirmanak/mealient/FirstSetUpTest.kt | 29 ++++------------ 2 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 app/src/androidTest/kotlin/gq/kirmanak/mealient/BaseTestCase.kt diff --git a/app/src/androidTest/kotlin/gq/kirmanak/mealient/BaseTestCase.kt b/app/src/androidTest/kotlin/gq/kirmanak/mealient/BaseTestCase.kt new file mode 100644 index 0000000..7025845 --- /dev/null +++ b/app/src/androidTest/kotlin/gq/kirmanak/mealient/BaseTestCase.kt @@ -0,0 +1,33 @@ +package gq.kirmanak.mealient + +import androidx.test.ext.junit.rules.activityScenarioRule +import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import dagger.hilt.android.testing.HiltAndroidRule +import gq.kirmanak.mealient.ui.activity.MainActivity +import okhttp3.mockwebserver.MockWebServer +import org.junit.After +import org.junit.Before +import org.junit.Rule + +abstract class BaseTestCase : TestCase() { + + @get:Rule(order = 0) + var hiltRule = HiltAndroidRule(this) + + @get:Rule(order = 1) + val mainActivityRule = activityScenarioRule() + + lateinit var mockWebServer: MockWebServer + + @Before + open fun setUp() { + mockWebServer = MockWebServer() + mockWebServer.start() + } + + @After + fun tearDown() { + mockWebServer.shutdown() + } + +} \ No newline at end of file diff --git a/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt b/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt index ff3d93e..8cf7100 100644 --- a/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt +++ b/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt @@ -1,38 +1,23 @@ package gq.kirmanak.mealient -import androidx.test.ext.junit.rules.activityScenarioRule -import com.kaspersky.kaspresso.testcases.api.testcase.TestCase -import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import gq.kirmanak.mealient.screen.BaseUrlScreen import gq.kirmanak.mealient.screen.DisclaimerScreen import gq.kirmanak.mealient.screen.RecipesListScreen -import gq.kirmanak.mealient.ui.activity.MainActivity -import okhttp3.mockwebserver.MockWebServer -import org.junit.Rule import org.junit.Test @HiltAndroidTest -class FirstSetUpTest : TestCase() { +class FirstSetUpTest : BaseTestCase() { - @get:Rule(order = 0) - var hiltRule = HiltAndroidRule(this) - - @get:Rule(order = 1) - val mainActivityRule = activityScenarioRule() - - private lateinit var mockWebServer: MockWebServer - - @Test - fun test() = before { - mockWebServer = MockWebServer() + override fun setUp() { + super.setUp() mockWebServer.dispatch { url, _ -> if (url == "/api/app/about") versionV1Response else notFoundResponse } - mockWebServer.start() - }.after { - mockWebServer.shutdown() - }.run { + } + + @Test + fun test() = run { step("Ensure button is disabled") { DisclaimerScreen { okayButton { From 04690e75cb1a11f309063be8c8cf0133cdcec641 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Fri, 16 Dec 2022 22:16:53 +0100 Subject: [PATCH 07/15] Run UI tests in check pipeline --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 648b522..2182cbf 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -22,7 +22,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew check coverageReport sonar --no-configuration-cache + run: ./gradlew allDevicesCheck check coverageReport sonar --no-configuration-cache - name: Publish test reports uses: mikepenz/action-junit-report@v3 From 0b199184a883c773849f764193710cd520822027 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Fri, 16 Dec 2022 22:25:30 +0100 Subject: [PATCH 08/15] Add Android SDK setup step --- .github/workflows/check.yml | 5 ++++- .github/workflows/sign.yml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 2182cbf..8b8aada 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -18,11 +18,14 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + - name: Run tests env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew allDevicesCheck check coverageReport sonar --no-configuration-cache + run: ./gradlew allDevicesCheck check coverageReport sonar --no-configuration-cache --no-daemon - name: Publish test reports uses: mikepenz/action-junit-report@v3 diff --git a/.github/workflows/sign.yml b/.github/workflows/sign.yml index a06d89e..6172429 100644 --- a/.github/workflows/sign.yml +++ b/.github/workflows/sign.yml @@ -21,6 +21,9 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + - name: Sign APK env: MEALIENT_KEY_STORE: ${{ secrets.MEALIENT_KEY_STORE }} @@ -36,7 +39,7 @@ jobs: echo "storePassword=$MEALIENT_KEY_STORE_PASSWORD" >> keystore.properties echo "keyAlias=$MEALIENT_KEY_ALIAS" >> keystore.properties echo "keyPassword=$MEALIENT_KEY_PASSWORD" >> keystore.properties - ./gradlew build bundle coverageReport sonar uploadToAppSweepRelease --no-configuration-cache + ./gradlew build bundle coverageReport sonar uploadToAppSweepRelease --no-configuration-cache --no-daemon cp app/build/outputs/apk/release/*.apk mealient-release.apk cp app/build/outputs/bundle/release/*.aab mealient-release.aab From ee8c07697425c79cc94d0a81ed9a19934f43b0ad Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 17 Dec 2022 11:36:40 +0100 Subject: [PATCH 09/15] Add magic Gradle Managed Devices flag --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 8b8aada..9c298c0 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -25,7 +25,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew allDevicesCheck check coverageReport sonar --no-configuration-cache --no-daemon + run: ./gradlew allDevicesCheck check coverageReport sonar --no-configuration-cache --no-daemon -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" - name: Publish test reports uses: mikepenz/action-junit-report@v3 From 67bd83bf446ebd43834ab9824def3e6d8bbb7497 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 17 Dec 2022 11:44:33 +0100 Subject: [PATCH 10/15] Extract URLs dispatcher to a separate method --- .../kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt b/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt index 8cf7100..a6b5e96 100644 --- a/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt +++ b/app/src/androidTest/kotlin/gq/kirmanak/mealient/FirstSetUpTest.kt @@ -4,13 +4,14 @@ import dagger.hilt.android.testing.HiltAndroidTest import gq.kirmanak.mealient.screen.BaseUrlScreen import gq.kirmanak.mealient.screen.DisclaimerScreen import gq.kirmanak.mealient.screen.RecipesListScreen +import org.junit.Before import org.junit.Test @HiltAndroidTest class FirstSetUpTest : BaseTestCase() { - override fun setUp() { - super.setUp() + @Before + fun dispatchUrls() { mockWebServer.dispatch { url, _ -> if (url == "/api/app/about") versionV1Response else notFoundResponse } From d0d3d2a144075c8d007fd437fc2a759316e9795c Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 17 Dec 2022 11:46:56 +0100 Subject: [PATCH 11/15] Run UI tests on macos runners --- .github/workflows/check.yml | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 9c298c0..3557006 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -18,17 +18,35 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 - - name: Setup Android SDK - uses: android-actions/setup-android@v2 - - name: Run tests env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew allDevicesCheck check coverageReport sonar --no-configuration-cache --no-daemon -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" + run: ./gradlew check coverageReport sonar --no-configuration-cache --no-daemon - name: Publish test reports uses: mikepenz/action-junit-report@v3 if: always() # always run even if the previous step fails with: - report_paths: './**/build/test-results/**/TEST-*.xml' \ No newline at end of file + report_paths: './**/build/test-results/**/TEST-*.xml' + + uiTests: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + + - name: Run tests + run: ./gradlew allDevicesCheck --no-daemon -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" \ No newline at end of file From bae4fe71cbb5a939946ca804c9dfaebe99f4c2bc Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 17 Dec 2022 11:52:32 +0100 Subject: [PATCH 12/15] Remove unused GITHUB_TOKEN from jobs --- .github/workflows/check.yml | 1 - .github/workflows/sign.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3557006..2f62492 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -20,7 +20,6 @@ jobs: - name: Run tests env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: ./gradlew check coverageReport sonar --no-configuration-cache --no-daemon diff --git a/.github/workflows/sign.yml b/.github/workflows/sign.yml index 6172429..78d10a6 100644 --- a/.github/workflows/sign.yml +++ b/.github/workflows/sign.yml @@ -30,7 +30,6 @@ jobs: MEALIENT_KEY_STORE_PASSWORD: ${{ secrets.MEALIENT_KEY_STORE_PASSWORD }} MEALIENT_KEY_ALIAS: ${{ secrets.MEALIENT_KEY_ALIAS }} MEALIENT_KEY_PASSWORD: ${{ secrets.MEALIENT_KEY_PASSWORD }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} APPSWEEP_API_KEY: ${{ secrets.APPSWEEP_API_KEY }} run: | From 01d7748c5878621b825b25f53e94d29adf09404b Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 17 Dec 2022 11:54:03 +0100 Subject: [PATCH 13/15] Increase test timeout to avoid false-negative --- .../src/main/kotlin/gq/kirmanak/mealient/test/BaseUnitTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/src/main/kotlin/gq/kirmanak/mealient/test/BaseUnitTest.kt b/testing/src/main/kotlin/gq/kirmanak/mealient/test/BaseUnitTest.kt index 50bc7b0..da5291f 100644 --- a/testing/src/main/kotlin/gq/kirmanak/mealient/test/BaseUnitTest.kt +++ b/testing/src/main/kotlin/gq/kirmanak/mealient/test/BaseUnitTest.kt @@ -22,7 +22,7 @@ open class BaseUnitTest { val instantExecutorRule = InstantTaskExecutorRule() @get:Rule(order = 1) - val timeoutRule: Timeout = Timeout.seconds(10) + val timeoutRule: Timeout = Timeout.seconds(20) protected val logger: Logger = FakeLogger() From 76cec72a66419df188f57bd50eeaf8d3c2183959 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 17 Dec 2022 12:06:14 +0100 Subject: [PATCH 14/15] Try to disable magic Gradle Managed Devices flag --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 2f62492..32c1957 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -48,4 +48,4 @@ jobs: uses: android-actions/setup-android@v2 - name: Run tests - run: ./gradlew allDevicesCheck --no-daemon -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" \ No newline at end of file + run: ./gradlew allDevicesCheck --no-daemon \ No newline at end of file From 2e951dad05806d0dc9185900a80c5c02fcbd1a13 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 17 Dec 2022 12:23:46 +0100 Subject: [PATCH 15/15] Revert "Try to disable magic Gradle Managed Devices flag" This reverts commit 76cec72a66419df188f57bd50eeaf8d3c2183959. --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 32c1957..2f62492 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -48,4 +48,4 @@ jobs: uses: android-actions/setup-android@v2 - name: Run tests - run: ./gradlew allDevicesCheck --no-daemon \ No newline at end of file + run: ./gradlew allDevicesCheck --no-daemon -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" \ No newline at end of file