From 1bf680c9e0a89720d23033d070a0d2909c023485 Mon Sep 17 00:00:00 2001 From: Michael Totschnig Date: Sat, 18 Nov 2023 21:33:49 +0100 Subject: [PATCH] Make Locale-specific test more reliable by deactivating LeakCanary and by granting CHANGE_CONFIGURATION permission before running LocaleTestRule --- Screengrabfile | 6 +- myExpenses/build.gradle | 4 +- .../myexpenses/test/screenshots/ArabTest.kt | 24 +----- .../test/screenshots/JapaneseTest.kt | 22 +---- .../test/screenshots/ScreenGrabTest.kt | 21 +++-- .../myexpenses/test/screenshots/TestMain.kt | 80 +++++++++++++------ .../org/totschnig/myexpenses/MyApplication.kt | 1 - 7 files changed, 78 insertions(+), 80 deletions(-) diff --git a/Screengrabfile b/Screengrabfile index 1b817ffcca..88fce67615 100644 --- a/Screengrabfile +++ b/Screengrabfile @@ -1,4 +1,4 @@ -# ./gradlew assembleExternDebug assembleAndroidTest +# ./gradlew assembleExternDebug assembleExternAndroidTest # fastlane screengrab app_package_name('org.totschnig.myexpenses.debug') @@ -52,4 +52,6 @@ clear_previous_screenshots(false) use_timestamp_suffix(false) -launch_arguments(["scenario 1"]) +launch_arguments(["scenario 1", "screenshots true"]) + +# specific_device("emulator-5556") diff --git a/myExpenses/build.gradle b/myExpenses/build.gradle index fce5d25975..279b34f9a3 100644 --- a/myExpenses/build.gradle +++ b/myExpenses/build.gradle @@ -154,7 +154,9 @@ tasks.withType(Test) { } dependencies { - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' + //LeakCanary leads to failure of Screengrab's LocaleUtil: + //https://github.com/fastlane/fastlane/issues/19521#issuecomment-1170017435 + //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' implementation("androidx.datastore:datastore-preferences:1.0.0") coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$desugarVersion" implementation project(':transactionscontract') diff --git a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/ArabTest.kt b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/ArabTest.kt index e40d282dbd..7eaa972799 100644 --- a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/ArabTest.kt +++ b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/ArabTest.kt @@ -1,29 +1,13 @@ package org.totschnig.myexpenses.test.screenshots -import android.Manifest -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.rule.GrantPermissionRule -import org.junit.Rule +import androidx.test.filters.LargeTest import org.junit.Test -import tools.fastlane.screengrab.locale.LocaleTestRule -class ArabTest: TestMain() { - - @get:Rule - val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CALENDAR - ) - - @Rule - @JvmField - val localeTestRule = LocaleTestRule(locale) +@LargeTest +class ArabTest: TestMain("ar-SA") { @Test fun runArab() { - runScenario("1", locale) - } - - companion object { - const val locale = "ar-SA" + runScenario("1") } } \ No newline at end of file diff --git a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/JapaneseTest.kt b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/JapaneseTest.kt index 01789a4b5e..b5a226158c 100644 --- a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/JapaneseTest.kt +++ b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/JapaneseTest.kt @@ -1,28 +1,14 @@ package org.totschnig.myexpenses.test.screenshots -import android.Manifest -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.rule.GrantPermissionRule -import org.junit.Rule +import androidx.test.filters.LargeTest import org.junit.Test -import tools.fastlane.screengrab.locale.LocaleTestRule -class JapaneseTest: TestMain() { - @get:Rule - val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CALENDAR - ) - @Rule - @JvmField - val localeTestRule = LocaleTestRule(locale) +@LargeTest +class JapaneseTest: TestMain("ja-JP") { @Test fun runJapanese() { - runScenario("1", locale) - } - - companion object { - const val locale = "ja-JP" + runScenario("1") } } \ No newline at end of file diff --git a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/ScreenGrabTest.kt b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/ScreenGrabTest.kt index a4c2ab54b3..e56cb7e751 100644 --- a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/ScreenGrabTest.kt +++ b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/ScreenGrabTest.kt @@ -1,32 +1,29 @@ package org.totschnig.myexpenses.test.screenshots -import android.Manifest +import androidx.test.filters.LargeTest import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.rule.GrantPermissionRule import org.junit.Rule import org.junit.Test import tools.fastlane.screengrab.locale.LocaleTestRule -import tools.fastlane.screengrab.locale.LocaleUtil /** * When not run from ScreenGrab, it runs with device locale */ -class ScreenGrabTest: TestMain() { - - @get:Rule - val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant( - Manifest.permission.WRITE_CALENDAR, Manifest.permission.READ_CALENDAR - ) +@LargeTest +class ScreenGrabTest: TestMain(null) { @Rule @JvmField val localeTestRule = LocaleTestRule() - override val shouldTakeScreenShot = true + override val shouldTakeScreenShot = getInstrumentationArgument("screenshots", "1") == "true" @Test fun mkScreenshots() { - val scenario = InstrumentationRegistry.getArguments().getString("scenario", "1") - runScenario(scenario, LocaleUtil.getTestLocale()) + val scenario = getInstrumentationArgument("scenario", "1") + runScenario(scenario) } + + private fun getInstrumentationArgument(key: String, @Suppress("SameParameterValue") defaultValue: String) = + InstrumentationRegistry.getArguments().getString(key, defaultValue) } \ No newline at end of file diff --git a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/TestMain.kt b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/TestMain.kt index 849591ac37..5f898b2dec 100644 --- a/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/TestMain.kt +++ b/myExpenses/src/androidTest/java/org/totschnig/myexpenses/test/screenshots/TestMain.kt @@ -1,8 +1,8 @@ package org.totschnig.myexpenses.test.screenshots -import android.content.res.Configuration -import android.content.res.Resources -import androidx.appcompat.app.AppCompatDelegate +import android.Manifest +import android.content.Context +import android.os.Build import androidx.compose.ui.test.assertTextContains import androidx.compose.ui.test.filter import androidx.compose.ui.test.hasText @@ -10,7 +10,6 @@ import androidx.compose.ui.test.onChildren import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick -import androidx.core.os.LocaleListCompat import androidx.recyclerview.widget.RecyclerView import androidx.test.espresso.Espresso.closeSoftKeyboard import androidx.test.espresso.Espresso.onIdle @@ -25,11 +24,16 @@ import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import androidx.test.rule.GrantPermissionRule import androidx.test.uiautomator.UiDevice import org.hamcrest.Matchers.containsString import org.junit.After import org.junit.AfterClass +import org.junit.Before import org.junit.BeforeClass +import org.junit.Rule +import org.junit.rules.RuleChain +import org.junit.rules.TestRule import org.totschnig.myexpenses.BuildConfig import org.totschnig.myexpenses.R import org.totschnig.myexpenses.preference.PrefKey @@ -39,25 +43,50 @@ import org.totschnig.myexpenses.testutils.withPositionInParent import org.totschnig.myexpenses.util.distrib.DistributionHelper.versionNumber import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.cleanstatusbar.CleanStatusBar +import tools.fastlane.screengrab.locale.LocaleTestRule import tools.fastlane.screengrab.locale.LocaleUtil - -abstract class TestMain : BaseMyExpensesTest() { +import java.util.Locale + +abstract class TestMain(locale: String?) : BaseMyExpensesTest() { + + @Rule + @JvmField + val chain: TestRule = RuleChain + .outerRule( + GrantPermissionRule.grant( + *buildList { + add(Manifest.permission.WRITE_CALENDAR) + add(Manifest.permission.READ_CALENDAR) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + add(Manifest.permission.POST_NOTIFICATIONS) + } + add(Manifest.permission.CHANGE_CONFIGURATION) + }.toTypedArray() + ) + ) + .around( + locale?.let { LocaleTestRule(it) } ?: LocaleTestRule() + ) open val shouldTakeScreenShot = false + + @Before + fun configureLocale() { + LocaleUtil.localeFromString(LocaleUtil.getTestLocale())?.let { + //targetContext.updateWith(it) + testContext.updateWith(it) + } + } + @After fun cleanUp() { app.fixture.cleanup(contentResolver) } - fun runScenario(scenario: String, locale: String?) { - loadFixture(scenario == "2", locale) + fun runScenario(scenario: String) { + loadFixture(scenario == "2") scenario(scenario) - testScenario.onActivity { - it.runOnUiThread { - AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList()) - } - } } private fun drawerAction(action: ViewAction) { @@ -82,6 +111,10 @@ abstract class TestMain : BaseMyExpensesTest() { listNode.onChildren().onFirst() .assertTextContains(getString(R.string.split_transaction), substring = true) clickContextItem(R.string.details) + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { + //https://github.com/android/android-test/issues/444 + Thread.sleep(500) + } onView(withId(android.R.id.button1)).perform(click()) closeSoftKeyboard() takeScreenshot("split") @@ -118,7 +151,9 @@ abstract class TestMain : BaseMyExpensesTest() { onView(withText(containsString("Drive"))).perform(click()) onView(withText(containsString("Dropbox"))).perform(click()) onView(withText(containsString("WebDAV"))).perform(scrollTo(), click()) - Thread.sleep(5000) + if(shouldTakeScreenShot) { + Thread.sleep(5000) + } takeScreenshot("sync") } @@ -150,15 +185,7 @@ abstract class TestMain : BaseMyExpensesTest() { } } - private fun loadFixture(withPicture: Boolean, locale: String?) { - //LocaleTestRule only configure for app context, fixture loads resources from instrumentation context - LocaleUtil.localeFromString(locale)?.let { - AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(it)) - val config = Configuration() - config.locale = it - testContext.resources.update(config) - targetContext.resources.update(config) - } + private fun loadFixture(withPicture: Boolean) { unlock() app.fixture.setup(withPicture, repository, app.appComponent.plannerUtils(), homeCurrency) prefHandler.putInt(PrefKey.CURRENT_VERSION, versionNumber) @@ -166,11 +193,12 @@ abstract class TestMain : BaseMyExpensesTest() { launch(app.fixture.account1.id) } - private fun Resources.update(configuration: Configuration) { - updateConfiguration(configuration, displayMetrics) + private fun Context.updateWith(locale: Locale) { + val config = resources.configuration + config.setLocale(locale) + resources.updateConfiguration(config, resources.displayMetrics) } - private fun takeScreenshot(fileName: String) { if (shouldTakeScreenShot) { onIdle() diff --git a/myExpenses/src/main/java/org/totschnig/myexpenses/MyApplication.kt b/myExpenses/src/main/java/org/totschnig/myexpenses/MyApplication.kt index 024ea31dc2..f1eb09dc6d 100644 --- a/myExpenses/src/main/java/org/totschnig/myexpenses/MyApplication.kt +++ b/myExpenses/src/main/java/org/totschnig/myexpenses/MyApplication.kt @@ -32,7 +32,6 @@ import androidx.core.database.getLongOrNull import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner -import com.google.android.material.color.DynamicColors import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch