diff --git a/.github/workflows/e2e-test-backend.yml b/.github/workflows/e2e-test-backend.yml new file mode 100644 index 00000000000..87f3b2086ce --- /dev/null +++ b/.github/workflows/e2e-test-backend.yml @@ -0,0 +1,39 @@ +name: Backend Checks + +on: + workflow_dispatch: + +env: + BUILD_CACHE_AWS_REGION: ${{ secrets.BUILD_CACHE_AWS_REGION }} + BUILD_CACHE_AWS_BUCKET: ${{ secrets.BUILD_CACHE_AWS_BUCKET }} + BUILD_CACHE_AWS_ACCESS_KEY_ID: ${{ secrets.BUILD_CACHE_AWS_ACCESS_KEY_ID }} + BUILD_CACHE_AWS_SECRET_KEY: ${{ secrets.BUILD_CACHE_AWS_SECRET_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + test-backend-integration: + name: Test Backend Integration + runs-on: ubuntu-24.04 + env: + ANDROID_API_LEVEL: 34 + steps: + - uses: actions/checkout@v4.2.2 + - uses: GetStream/android-ci-actions/actions/setup-java@main + - uses: GetStream/android-ci-actions/actions/enable-kvm@main + - uses: GetStream/android-ci-actions/actions/setup-ruby@main + - name: Run tests + uses: reactivecircus/android-emulator-runner@v2 + timeout-minutes: 45 + with: + api-level: ${{ env.ANDROID_API_LEVEL }} + disable-animations: true + profile: pixel + arch : x86_64 + emulator-options: -no-snapshot-save -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect -camera-back none -camera-front none + script: bundle exec fastlane build_and_run_e2e_test use_backend:true + - name: Upload test results + uses: actions/upload-artifact@v4.4.3 + if: failure() + with: + name: logs_${{ env.ANDROID_API_LEVEL }} + path: fastlane/stream-chat-test-mock-server/logs/* diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 83076de705e..26b0ddf7da9 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -65,7 +65,9 @@ lane :build_and_run_e2e_test do |options| batch: options[:batch], batch_count: options[:batch_count], local_server: options[:local_server], - mock_server_branch: options[:mock_server_branch] + mock_server_branch: options[:mock_server_branch], + apk_folder_path: "../#{test_flavor}/build/outputs/apk", + use_backend: options[:use_backend] ) end @@ -90,7 +92,7 @@ lane :run_e2e_test do |options| install_test_services upload_attachments - stream_apk_folder_path = is_ci ? '..' : "../#{test_flavor}/build/outputs/apk" + stream_apk_folder_path = options[:apk_folder_path] || '..' stream_app_path = "#{stream_apk_folder_path}/e2e/debug/stream-chat-android-compose-sample-e2e-debug.apk" stream_test_path = "#{stream_apk_folder_path}/androidTest/e2e/debug/stream-chat-android-compose-sample-e2e-debug-androidTest.apk" sh("adb install -r #{stream_app_path}") @@ -102,16 +104,24 @@ lane :run_e2e_test do |options| orchestrator_package_name = 'androidx.test.orchestrator/.AndroidTestOrchestrator' androidx_test_services_path = sh('adb shell pm path androidx.test.services').strip - run_tests_in_batches = batch_tests( - batch: options[:batch], - batch_count: options[:batch_count], - test_apk_path: stream_test_path - ) + backend_test_class = 'io.getstream.chat.android.compose.tests.BackendTests' + + if options[:use_backend] + test_filter = "-e class #{backend_test_class}" + else + run_tests_in_batches = batch_tests( + batch: options[:batch], + batch_count: options[:batch_count], + test_apk_path: stream_test_path + ) + exclude_backend = "-e notClass #{backend_test_class}" + test_filter = run_tests_in_batches.empty? ? exclude_backend : "#{run_tests_in_batches} #{exclude_backend}" + end result = sh( "adb shell 'CLASSPATH=#{androidx_test_services_path}' " \ 'app_process / androidx.test.services.shellexecutor.ShellMain am instrument -w -e clearPackageData true ' \ - "-e targetInstrumentation #{test_package_name}/#{runner_package_name} #{run_tests_in_batches} #{orchestrator_package_name}" + "-e targetInstrumentation #{test_package_name}/#{runner_package_name} #{test_filter} #{orchestrator_package_name}" ) sh("adb exec-out sh -c 'cd #{adb_test_results_path} && tar cf - #{allure_results_path}' | tar xvf - -C .") if is_ci diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobot.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobot.kt index f939c19b8ec..bcaefde2cd1 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobot.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobot.kt @@ -117,7 +117,7 @@ class UserRobot { fun deleteMessage(messageCellIndex: Int = 0, hard: Boolean = false): UserRobot { openContextMenu(messageCellIndex) ContextMenu.delete.waitToAppear().click() - ContextMenu.ok.findObject().click() + ContextMenu.ok.waitToAppear().click() return this } diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/BackendTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/BackendTests.kt new file mode 100644 index 00000000000..77e232f8052 --- /dev/null +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/BackendTests.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2014-2025 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * 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 io.getstream.chat.android.compose.tests + +import io.getstream.chat.android.compose.robots.assertDeletedMessage +import io.getstream.chat.android.compose.robots.assertMessage +import io.getstream.chat.android.compose.robots.assertReaction +import io.getstream.chat.android.compose.sample.ui.InitTestActivity +import io.getstream.chat.android.e2e.test.mockserver.ReactionType +import io.qameta.allure.kotlin.Allure.step +import org.junit.Test + +class BackendTests : StreamTestCase() { + + override var useMockServer = false + override fun initTestActivity() = InitTestActivity.UserLogin + + @Test + fun test_message() { + val originalMessage = "hi" + val editedMessage = "hello" + + step("GIVEN user opens a channel") { + userRobot.login().openChannel() + } + step("WHEN user sends a message") { + userRobot.sendMessage(originalMessage) + } + step("THEN message appears") { + userRobot.assertMessage(originalMessage) + } + step("WHEN user edits the message") { + userRobot.editMessage(editedMessage) + } + step("THEN the message is edited") { + userRobot.assertMessage(editedMessage) + } + step("WHEN user deletes the message") { + userRobot.deleteMessage() + } + step("THEN the message is deleted") { + userRobot.assertDeletedMessage() + } + } + + @Test + fun test_reaction() { + val message = "test" + + step("GIVEN user opens the channel") { + userRobot.login().openChannel() + } + step("WHEN user sends the message") { + userRobot.sendMessage(message) + } + step("AND user adds the reaction") { + userRobot.addReaction(type = ReactionType.LIKE) + } + step("THEN the reaction is added") { + userRobot.assertReaction(type = ReactionType.LIKE, isDisplayed = true) + } + step("WHEN user removes the reaction") { + userRobot.deleteReaction(type = ReactionType.LIKE) + } + step("THEN the reaction is removed") { + userRobot.assertReaction(type = ReactionType.LIKE, isDisplayed = false) + } + } +} diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/StreamTestCase.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/StreamTestCase.kt index 4c5a21d0e3b..dcad958a2d5 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/StreamTestCase.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/StreamTestCase.kt @@ -41,6 +41,7 @@ import org.junit.rules.TestName abstract class StreamTestCase { lateinit var mockServer: MockServer + open var useMockServer = true val userRobot = UserRobot() lateinit var backendRobot: BackendRobot lateinit var participantRobot: ParticipantRobot @@ -53,16 +54,20 @@ abstract class StreamTestCase { @Before fun setUp() { - mockServer = MockServer(testName.methodName) - backendRobot = BackendRobot(mockServer) - participantRobot = ParticipantRobot(mockServer) + if (useMockServer) { + mockServer = MockServer(testName.methodName) + backendRobot = BackendRobot(mockServer) + participantRobot = ParticipantRobot(mockServer) + } startApp() grantAppPermissions() } @After fun tearDown() { - mockServer.stop() + if (useMockServer) { + mockServer.stop() + } } @SuppressLint("InlinedApi") @@ -84,8 +89,10 @@ abstract class StreamTestCase { private fun startApp() { testContext.packageManager.getLaunchIntentForPackage(packageName)?.let { it.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) - it.putExtra("BASE_URL", mockServer.url) it.putExtra("InitTestActivity", initTestActivity()) + if (useMockServer) { + it.putExtra("BASE_URL", mockServer.url) + } testContext.startActivity(it) } ?: throw IllegalStateException("No launch intent found for package: $packageName") }