From 7b5506fa477e0c550ebf9e579f652fa76ee83ead Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Wed, 22 Nov 2023 22:12:08 -0500 Subject: [PATCH 1/2] Add UI tests for Emoji Search Android frontends The view-based sample is currently crashing, which is why it's ignored. --- .github/workflows/build.yaml | 6 ++ .../counter/android-composeui/build.gradle | 1 + samples/counter/android-views/build.gradle | 1 + .../android-composeui/build.gradle | 9 +++ .../android/composeui/EmojiSearchUiTest.kt | 21 +++++++ .../emoji-search/android-tests/build.gradle | 12 ++++ .../tests/AbstractEmojiSearchUiTest.kt | 59 +++++++++++++++++++ .../emoji-search/android-views/build.gradle | 14 ++++- .../android/views/EmojiSearchUiTest.kt | 23 ++++++++ settings.gradle | 1 + 10 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 samples/emoji-search/android-composeui/src/androidTest/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchUiTest.kt create mode 100644 samples/emoji-search/android-tests/build.gradle create mode 100644 samples/emoji-search/android-tests/src/main/kotlin/com/example/redwood/emojisearch/android/tests/AbstractEmojiSearchUiTest.kt create mode 100644 samples/emoji-search/android-views/src/androidTest/kotlin/com/example/redwood/emojisearch/android/views/EmojiSearchUiTest.kt diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 23c4c73f3c..c9d73d5aba 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -134,6 +134,12 @@ jobs: - run: ./gradlew -p samples/emoji-search build + - uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 29 + emulator-boot-timeout: 20000 + script: ./gradlew -p samples/emoji-search connectedCheck + - name: Build Emoji Search iOS (UIKit) run: xcodebuild -project samples/emoji-search/ios-uikit/EmojiSearchApp.xcodeproj -scheme EmojiSearchApp -destination 'platform=iOS Simulator,name=iPhone 12,OS=latest' diff --git a/samples/counter/android-composeui/build.gradle b/samples/counter/android-composeui/build.gradle index f66b4ac6a6..18203a2b9c 100644 --- a/samples/counter/android-composeui/build.gradle +++ b/samples/counter/android-composeui/build.gradle @@ -29,6 +29,7 @@ android { } buildFeatures { + // Needed to pass application ID to UIAutomator tests. buildConfig = true } } diff --git a/samples/counter/android-views/build.gradle b/samples/counter/android-views/build.gradle index f663b3cc74..72e589a888 100644 --- a/samples/counter/android-views/build.gradle +++ b/samples/counter/android-views/build.gradle @@ -24,6 +24,7 @@ android { } buildFeatures { + // Needed to pass application ID to UIAutomator tests. buildConfig = true } } diff --git a/samples/emoji-search/android-composeui/build.gradle b/samples/emoji-search/android-composeui/build.gradle index 13992bdee4..68b98557a8 100644 --- a/samples/emoji-search/android-composeui/build.gradle +++ b/samples/emoji-search/android-composeui/build.gradle @@ -8,8 +8,14 @@ redwoodBuild { android { namespace 'com.example.redwood.emojisearch.android.composeui' + defaultConfig { + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + } + buildFeatures { compose = true + // Needed to pass application ID to UIAutomator tests. + buildConfig = true } } @@ -33,4 +39,7 @@ dependencies { implementation projects.redwoodTreehouseHostComposeui implementation projects.redwoodWidgetCompose implementation libs.okio.assetfilesystem + + androidTestImplementation libs.androidx.test.runner + androidTestImplementation projects.samples.emojiSearch.androidTests } diff --git a/samples/emoji-search/android-composeui/src/androidTest/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchUiTest.kt b/samples/emoji-search/android-composeui/src/androidTest/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchUiTest.kt new file mode 100644 index 0000000000..54aa700c03 --- /dev/null +++ b/samples/emoji-search/android-composeui/src/androidTest/kotlin/com/example/redwood/emojisearch/android/composeui/EmojiSearchUiTest.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.redwood.emojisearch.android.composeui + +import com.example.redwood.emojisearch.android.composeui.BuildConfig.APPLICATION_ID +import com.example.redwood.emojisearch.android.tests.AbstractEmojiSearchUiTest + +class EmojiSearchUiTest : AbstractEmojiSearchUiTest(APPLICATION_ID) diff --git a/samples/emoji-search/android-tests/build.gradle b/samples/emoji-search/android-tests/build.gradle new file mode 100644 index 0000000000..4f9f3b8f46 --- /dev/null +++ b/samples/emoji-search/android-tests/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.library' +apply plugin: 'org.jetbrains.kotlin.android' + +dependencies { + implementation(libs.junit) + implementation(libs.androidx.test.core) + implementation(libs.androidx.test.uiautomator) +} + +android { + namespace 'com.example.redwood.emojisearch.android.tests' +} diff --git a/samples/emoji-search/android-tests/src/main/kotlin/com/example/redwood/emojisearch/android/tests/AbstractEmojiSearchUiTest.kt b/samples/emoji-search/android-tests/src/main/kotlin/com/example/redwood/emojisearch/android/tests/AbstractEmojiSearchUiTest.kt new file mode 100644 index 0000000000..e2ce59b7a4 --- /dev/null +++ b/samples/emoji-search/android-tests/src/main/kotlin/com/example/redwood/emojisearch/android/tests/AbstractEmojiSearchUiTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.redwood.emojisearch.android.tests + +import android.content.Context +import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK +import androidx.test.core.app.ApplicationProvider +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.By.pkg +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiSelector +import androidx.test.uiautomator.Until +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import org.junit.Before +import org.junit.Test + +abstract class AbstractEmojiSearchUiTest(private val appPackage: String) { + private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())!! + private val search get() = device.findObject(By.clazz("android.widget.EditText"))!! + + @Before fun before() { + val context = ApplicationProvider.getApplicationContext() + val intent = context.packageManager.getLaunchIntentForPackage(appPackage)!!.apply { + addFlags(FLAG_ACTIVITY_CLEAR_TASK) + } + context.startActivity(intent) + device.wait(Until.hasObject(pkg(appPackage).depth(0)), 5_000) + } + + @Test fun searchTrees() { + awaitText("0. +1") + search.text = "tree" + awaitText("301. christmas_tree") + search.clear() + awaitText("0. +1") + } + + private fun awaitText(value: String, duration: Duration = 1.seconds) { + val text = device.findObject(UiSelector().text(value)) + if (!text.waitForExists(duration.inWholeMilliseconds)) { + throw AssertionError("Waited $duration for \"$value\" but never appeared") + } + } +} diff --git a/samples/emoji-search/android-views/build.gradle b/samples/emoji-search/android-views/build.gradle index b638ba6fd1..19e4017b33 100644 --- a/samples/emoji-search/android-views/build.gradle +++ b/samples/emoji-search/android-views/build.gradle @@ -6,7 +6,16 @@ redwoodBuild { } android { - namespace "com.example.redwood.emojisearch.android.views" + namespace 'com.example.redwood.emojisearch.android.views' + + defaultConfig { + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + } + + buildFeatures { + // Needed to pass application ID to UIAutomator tests. + buildConfig = true + } } dependencies { @@ -23,4 +32,7 @@ dependencies { implementation projects.redwoodTreehouse implementation projects.redwoodTreehouseHost implementation libs.okio.assetfilesystem + + androidTestImplementation libs.androidx.test.runner + androidTestImplementation projects.samples.emojiSearch.androidTests } diff --git a/samples/emoji-search/android-views/src/androidTest/kotlin/com/example/redwood/emojisearch/android/views/EmojiSearchUiTest.kt b/samples/emoji-search/android-views/src/androidTest/kotlin/com/example/redwood/emojisearch/android/views/EmojiSearchUiTest.kt new file mode 100644 index 0000000000..0aaf99ab08 --- /dev/null +++ b/samples/emoji-search/android-views/src/androidTest/kotlin/com/example/redwood/emojisearch/android/views/EmojiSearchUiTest.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.redwood.emojisearch.android.views + +import com.example.redwood.emojisearch.android.tests.AbstractEmojiSearchUiTest +import com.example.redwood.emojisearch.android.views.BuildConfig.APPLICATION_ID +import org.junit.Ignore + +@Ignore("https://github.com/cashapp/redwood/issues/1696") +class EmojiSearchUiTest : AbstractEmojiSearchUiTest(APPLICATION_ID) diff --git a/settings.gradle b/settings.gradle index 11fa646cc8..8dafdb2f61 100644 --- a/settings.gradle +++ b/settings.gradle @@ -114,6 +114,7 @@ if (!hasProperty('redwoodNoApps')) { include ':samples:counter:shared-composeui' include ':samples:emoji-search:android-composeui' + include ':samples:emoji-search:android-tests' include ':samples:emoji-search:android-views' include ':samples:emoji-search:browser' include ':samples:emoji-search:ios-shared' From 6a0ea658d1b2c8408750bc29c877c06aebf00a6f Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Thu, 23 Nov 2023 00:40:45 -0500 Subject: [PATCH 2/2] Try a longer timeout since this goes through Zipline --- .../emojisearch/android/tests/AbstractEmojiSearchUiTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/emoji-search/android-tests/src/main/kotlin/com/example/redwood/emojisearch/android/tests/AbstractEmojiSearchUiTest.kt b/samples/emoji-search/android-tests/src/main/kotlin/com/example/redwood/emojisearch/android/tests/AbstractEmojiSearchUiTest.kt index e2ce59b7a4..3fe830e72d 100644 --- a/samples/emoji-search/android-tests/src/main/kotlin/com/example/redwood/emojisearch/android/tests/AbstractEmojiSearchUiTest.kt +++ b/samples/emoji-search/android-tests/src/main/kotlin/com/example/redwood/emojisearch/android/tests/AbstractEmojiSearchUiTest.kt @@ -50,7 +50,7 @@ abstract class AbstractEmojiSearchUiTest(private val appPackage: String) { awaitText("0. +1") } - private fun awaitText(value: String, duration: Duration = 1.seconds) { + private fun awaitText(value: String, duration: Duration = 4.seconds) { val text = device.findObject(UiSelector().text(value)) if (!text.waitForExists(duration.inWholeMilliseconds)) { throw AssertionError("Waited $duration for \"$value\" but never appeared")