diff --git a/platforms/android/library-compose/build.gradle b/platforms/android/library-compose/build.gradle index 367becde3..4fb78e3f1 100644 --- a/platforms/android/library-compose/build.gradle +++ b/platforms/android/library-compose/build.gradle @@ -94,6 +94,7 @@ dependencies { testImplementation libs.test.kotlin.coroutines testImplementation libs.test.turbine testImplementation libs.molecule.runtime + androidTestImplementation project(":test") androidTestImplementation libs.test.androidx.junit androidTestImplementation libs.test.androidx.espresso androidTestImplementation libs.test.mockk.android diff --git a/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorActionsTest.kt b/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorActionsTest.kt index f47106fda..7cfe4b28a 100644 --- a/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorActionsTest.kt +++ b/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorActionsTest.kt @@ -5,6 +5,7 @@ import io.element.android.wysiwyg.compose.testutils.ComposerActions import io.element.android.wysiwyg.compose.testutils.StateFactory import io.element.android.wysiwyg.compose.testutils.copy import io.element.android.wysiwyg.compose.testutils.showContent +import io.element.android.wysiwyg.test.rules.createFlakyEmulatorRule import io.element.android.wysiwyg.utils.NBSP import io.element.android.wysiwyg.view.models.InlineFormat import kotlinx.coroutines.test.runTest @@ -19,6 +20,9 @@ class RichTextEditorActionsTest { @get:Rule val composeTestRule = createComposeRule() + @get:Rule + val flakyEmulatorRule = createFlakyEmulatorRule() + @Test fun testBold() = runTest { val state = StateFactory.createState() diff --git a/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorStateTest.kt b/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorStateTest.kt index ad32c772f..6ee74019e 100644 --- a/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorStateTest.kt +++ b/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorStateTest.kt @@ -14,6 +14,7 @@ import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText +import io.element.android.wysiwyg.test.rules.createFlakyEmulatorRule import io.element.android.wysiwyg.utils.NBSP import io.element.android.wysiwyg.view.models.InlineFormat import kotlinx.coroutines.flow.MutableStateFlow @@ -27,6 +28,9 @@ class RichTextEditorStateTest { @get:Rule val composeTestRule = createComposeRule() + @get:Rule + val flakyEmulatorRule = createFlakyEmulatorRule() + @Test fun testSharingState() = runTest { val state = RichTextEditorState() diff --git a/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorStyleTest.kt b/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorStyleTest.kt index 407be4420..ae0759b97 100644 --- a/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorStyleTest.kt +++ b/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorStyleTest.kt @@ -13,6 +13,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.withText import io.element.android.wysiwyg.compose.testutils.StateFactory.createState import io.element.android.wysiwyg.compose.testutils.ViewMatchers.isRichTextEditor +import io.element.android.wysiwyg.test.rules.createFlakyEmulatorRule import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -22,6 +23,9 @@ class RichTextEditorStyleTest { @get:Rule val composeTestRule = createComposeRule() + @get:Rule + val flakyEmulatorRule = createFlakyEmulatorRule() + private val state = createState() private val bulletRadius = MutableStateFlow(2.dp) private val codeBgColor = MutableStateFlow(Color.Blue) diff --git a/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorTest.kt b/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorTest.kt index 95cbfea6d..3b5b2e9dc 100644 --- a/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorTest.kt +++ b/platforms/android/library-compose/src/androidTest/java/io/element/android/wysiwyg/compose/RichTextEditorTest.kt @@ -13,6 +13,7 @@ import io.element.android.wysiwyg.compose.testutils.StateFactory.createState import io.element.android.wysiwyg.compose.testutils.ViewMatchers.isRichTextEditor import io.element.android.wysiwyg.compose.testutils.copy import io.element.android.wysiwyg.compose.testutils.showContent +import io.element.android.wysiwyg.test.rules.createFlakyEmulatorRule import io.element.android.wysiwyg.view.models.LinkAction import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -26,6 +27,9 @@ class RichTextEditorTest { @get:Rule val composeTestRule = createAndroidComposeRule() + @get:Rule + val flakyEmulatorRule = createFlakyEmulatorRule() + @Test fun testTypeText() { val state = createState() diff --git a/platforms/android/library/build.gradle b/platforms/android/library/build.gradle index b8d3e15f9..cabec9ca3 100644 --- a/platforms/android/library/build.gradle +++ b/platforms/android/library/build.gradle @@ -107,6 +107,7 @@ dependencies { testImplementation libs.test.robolectric testImplementation libs.test.mockk testImplementation libs.test.hamcrest + androidTestImplementation project(":test") androidTestImplementation libs.test.androidx.junit androidTestImplementation libs.test.androidx.espresso androidTestImplementation libs.test.androidx.espresso.accessibility diff --git a/platforms/android/library/src/androidTest/java/io/element/android/wysiwyg/EditorEditTextInputTests.kt b/platforms/android/library/src/androidTest/java/io/element/android/wysiwyg/EditorEditTextInputTests.kt index 1510de3b0..1777defe9 100644 --- a/platforms/android/library/src/androidTest/java/io/element/android/wysiwyg/EditorEditTextInputTests.kt +++ b/platforms/android/library/src/androidTest/java/io/element/android/wysiwyg/EditorEditTextInputTests.kt @@ -30,6 +30,7 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.wysiwyg.display.TextDisplay import io.element.android.wysiwyg.test.R +import io.element.android.wysiwyg.test.rules.createFlakyEmulatorRule import io.element.android.wysiwyg.test.utils.* import io.element.android.wysiwyg.utils.RustErrorCollector import io.element.android.wysiwyg.view.models.InlineFormat @@ -54,6 +55,9 @@ class EditorEditTextInputTests { @get:Rule val scenarioRule = ActivityScenarioRule(TestActivity::class.java) + @get:Rule + val flakyEmulatorRule = createFlakyEmulatorRule() + private val ipsum = "Lorem Ipsum is simply dummy text of the printing and typesetting industry." init { diff --git a/platforms/android/settings.gradle b/platforms/android/settings.gradle index eefa67e59..18cb0bf31 100644 --- a/platforms/android/settings.gradle +++ b/platforms/android/settings.gradle @@ -13,4 +13,4 @@ dependencyResolutionManagement { } } rootProject.name = "Rich Text Editor" -include ':example-view', ':example-compose', ':library', ':library-compose' +include ':example-view', ':example-compose', ':library', ':library-compose', ':test' diff --git a/platforms/android/test/.gitignore b/platforms/android/test/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/platforms/android/test/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/platforms/android/test/build.gradle.kts b/platforms/android/test/build.gradle.kts new file mode 100644 index 000000000..f5d33d09a --- /dev/null +++ b/platforms/android/test/build.gradle.kts @@ -0,0 +1,34 @@ +@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "io.element.android.wysiwyg.test" + compileSdk = 34 + + defaultConfig { + minSdk = 21 + } + + buildTypes { + release { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +kotlin { + jvmToolchain(11) +} + +dependencies { + implementation(libs.test.androidx.uiautomator) + implementation(libs.test.junit) + implementation(libs.test.androidx.espresso) +} diff --git a/platforms/android/test/src/main/java/io/element/android/wysiwyg/test/rules/DismissAnrRule.kt b/platforms/android/test/src/main/java/io/element/android/wysiwyg/test/rules/DismissAnrRule.kt new file mode 100644 index 000000000..6ef74e87a --- /dev/null +++ b/platforms/android/test/src/main/java/io/element/android/wysiwyg/test/rules/DismissAnrRule.kt @@ -0,0 +1,29 @@ +package io.element.android.wysiwyg.test.rules + +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiObject +import androidx.test.uiautomator.UiSelector +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +internal class DismissAnrRule : TestWatcher() { + override fun starting(description: Description) { + dismissAnr() + } +} + +private fun dismissAnr() { + val device = UiDevice.getInstance(getInstrumentation()) + val dialog = device.findAnrDialog() + if (dialog.exists()) { + device.findWaitButton().click() + } +} + +private fun UiDevice.findAnrDialog(): UiObject = + findObject(UiSelector().textContains("isn't responding")) + +private fun UiDevice.findWaitButton(): UiObject = + findObject(UiSelector().text("Wait").enabled(true)) + .apply { waitForExists(5000) } \ No newline at end of file diff --git a/platforms/android/test/src/main/java/io/element/android/wysiwyg/test/rules/FlakyEmulatorRule.kt b/platforms/android/test/src/main/java/io/element/android/wysiwyg/test/rules/FlakyEmulatorRule.kt new file mode 100644 index 000000000..ffbad02a5 --- /dev/null +++ b/platforms/android/test/src/main/java/io/element/android/wysiwyg/test/rules/FlakyEmulatorRule.kt @@ -0,0 +1,11 @@ +package io.element.android.wysiwyg.test.rules + +import org.junit.rules.RuleChain +import org.junit.rules.TestRule + +/** + * Creates a rule that helps to reduce emulator related flakiness. + */ +fun createFlakyEmulatorRule(): TestRule = RuleChain + .outerRule(RetryOnFailureRule()) + .around(DismissAnrRule()) diff --git a/platforms/android/test/src/main/java/io/element/android/wysiwyg/test/rules/RetryOnFailureRule.kt b/platforms/android/test/src/main/java/io/element/android/wysiwyg/test/rules/RetryOnFailureRule.kt new file mode 100644 index 000000000..c3fe61e8b --- /dev/null +++ b/platforms/android/test/src/main/java/io/element/android/wysiwyg/test/rules/RetryOnFailureRule.kt @@ -0,0 +1,24 @@ +package io.element.android.wysiwyg.test.rules + +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +internal class RetryOnFailureRule : TestRule { + override fun apply( + base: Statement, + description: Description + ): Statement = + RetryStatement(base) +} + +private class RetryStatement(private val base: Statement) : Statement() { + override fun evaluate() { + try { + base.evaluate() + return + } catch (t: Throwable) { + base.evaluate() + } + } +}