From c14037818c5ad5000cf0e37459f7f4cfe889d819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Mion?= Date: Wed, 19 Aug 2026 15:04:04 +0100 Subject: [PATCH 1/5] Revert "e2e: Descope the mute and block state tests" This reverts commit 12bbb969a12280549f79f357c627a2022d715b4f. The mock server now sends the live own user in its health check, so the mute and block state these tests assert is stable. --- .../android/compose/pages/ChannelListPage.kt | 1 + .../android/compose/pages/MessageListPage.kt | 3 + .../chat/android/compose/robots/UserRobot.kt | 56 +++++++++++++ .../robots/UserRobotChannelListAsserts.kt | 9 +++ .../robots/UserRobotMessageListAsserts.kt | 25 ++++++ .../compose/tests/ChannelActionsTests.kt | 39 +++++++++ .../android/compose/tests/ModerationTests.kt | 80 ++++++++++++++++++- 7 files changed, 211 insertions(+), 2 deletions(-) diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/ChannelListPage.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/ChannelListPage.kt index 77b4f0e3e2c..fd2ff36a551 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/ChannelListPage.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/ChannelListPage.kt @@ -52,6 +52,7 @@ class ChannelListPage { companion object { val mute = By.desc("Mute") + val unmute = By.desc("Unmute") val more = By.desc("More") } } diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt index 548709ca111..78244a68306 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt @@ -171,7 +171,10 @@ open class MessageListPage { val flag get() = By.res("Stream_ContextMenu_Flag Message") val pin get() = By.res("Stream_ContextMenu_Pin to this Chat") val unpin get() = By.res("Stream_ContextMenu_Unpin from this Chat") + val muteUser get() = By.res("Stream_ContextMenu_Mute User") + val unmuteUser get() = By.res("Stream_ContextMenu_Unmute User") val block get() = By.res("Stream_ContextMenu_Block user") + val unblock get() = By.res("Stream_ContextMenu_Unblock user") val delete get() = By.res("Stream_ContextMenu_Delete Message") val showMoreReactions = By.desc("Show more reactions") val ok = By.text("OK") 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 56b53283adf..8586c838086 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 @@ -17,6 +17,7 @@ package io.getstream.chat.android.compose.robots import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector import io.getstream.chat.android.compose.pages.ChannelInfoPage import io.getstream.chat.android.compose.pages.ChannelListPage import io.getstream.chat.android.compose.pages.LoginPage @@ -36,6 +37,7 @@ import io.getstream.chat.android.e2e.test.uiautomator.device import io.getstream.chat.android.e2e.test.uiautomator.findObjects import io.getstream.chat.android.e2e.test.uiautomator.isDisplayed import io.getstream.chat.android.e2e.test.uiautomator.longPress +import io.getstream.chat.android.e2e.test.uiautomator.seconds import io.getstream.chat.android.e2e.test.uiautomator.sleep import io.getstream.chat.android.e2e.test.uiautomator.swipeDown import io.getstream.chat.android.e2e.test.uiautomator.swipeUp @@ -201,6 +203,24 @@ class UserRobot { return this } + /** + * Opens the message menu of the message with [text], reopening it while [option] is missing. + * The menu builds its options when it opens and keeps them while it stays open, so an option + * that flips because of a moderation action shows up only on a later open. + */ + internal fun openContextMenuWithOption(text: String, option: BySelector): UserRobot { + repeat(contextMenuOpenAttempts) { attempt -> + openContextMenu(text) + if (option.waitDisplayed(timeOutMillis = 5.seconds)) { + return this + } + if (attempt < contextMenuOpenAttempts - 1) { + pressBack() + } + } + return this + } + fun flagMessage(text: String): UserRobot { openContextMenu(text) ContextMenu.flag.waitToAppearAndClick() @@ -212,6 +232,30 @@ class UserRobot { return this } + fun muteMessageAuthor(text: String): UserRobot { + openContextMenu(text) + ContextMenu.muteUser.waitToAppearAndClick() + return this + } + + fun unmuteMessageAuthor(text: String): UserRobot { + openContextMenuWithOption(text, ContextMenu.unmuteUser) + ContextMenu.unmuteUser.waitToAppearAndClick() + return this + } + + fun blockMessageAuthor(text: String): UserRobot { + openContextMenu(text) + ContextMenu.block.waitToAppearAndClick() + return this + } + + fun unblockMessageAuthor(text: String): UserRobot { + openContextMenuWithOption(text, ContextMenu.unblock) + ContextMenu.unblock.waitToAppearAndClick() + return this + } + fun pinMessage(messageCellIndex: Int = 0): UserRobot { openContextMenu(messageCellIndex) ContextMenu.pin.waitToAppearAndClick() @@ -401,6 +445,16 @@ class UserRobot { return this } + fun tapOnMuteSwipeAction(): UserRobot { + ChannelListPage.ChannelList.SwipeActions.mute.waitToAppearAndClick() + return this + } + + fun tapOnUnmuteSwipeAction(): UserRobot { + ChannelListPage.ChannelList.SwipeActions.unmute.waitToAppearAndClick() + return this + } + fun tapOnLeaveGroup(): UserRobot { ChannelListPage.ChannelMenu.leaveGroup.waitToAppearAndClick() return this @@ -598,3 +652,5 @@ class UserRobot { return this } } + +private const val contextMenuOpenAttempts = 3 diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotChannelListAsserts.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotChannelListAsserts.kt index 5e8c911217d..61f6368afec 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotChannelListAsserts.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotChannelListAsserts.kt @@ -80,6 +80,15 @@ fun UserRobot.assertFailedMessageDeliveryStatusInPreview(): UserRobot { return this } +fun UserRobot.assertChannelIsMuted(isMuted: Boolean): UserRobot { + if (isMuted) { + assertTrue(Channel.mutedIcon.waitDisplayed()) + } else { + assertFalse(Channel.mutedIcon.waitToDisappear().isDisplayed()) + } + return this +} + fun UserRobot.assertChannelListIsEmpty(): UserRobot { assertFalse(ChannelList.channels.waitToDisappear().isDisplayed()) return this diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt index 46dd859379c..9651cb0dee5 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt @@ -24,6 +24,7 @@ import io.getstream.chat.android.compose.R import io.getstream.chat.android.compose.pages.MessageListPage import io.getstream.chat.android.compose.pages.MessageListPage.Composer import io.getstream.chat.android.compose.pages.MessageListPage.MessageList.Message +import io.getstream.chat.android.compose.pages.MessageListPage.MessageList.Message.ContextMenu import io.getstream.chat.android.compose.pages.ThreadPage import io.getstream.chat.android.e2e.test.mockserver.MessageDeliveryStatus import io.getstream.chat.android.e2e.test.mockserver.ReactionType @@ -552,3 +553,27 @@ fun UserRobot.assertFlagMessageDialog(isDisplayed: Boolean): UserRobot { assertVisibility(MessageListPage.FlagMessageDialog.body, isDisplayed) return this } + +/** + * Opens the message menu of the message with [messageText] and asserts the mute option it offers + * for the author. The sample has no muted users list, so the option label is the only place the + * mute state is visible. + */ +fun UserRobot.assertMuteMessageAuthorOption(messageText: String, isAuthorMuted: Boolean): UserRobot { + val expectedOption = if (isAuthorMuted) ContextMenu.unmuteUser else ContextMenu.muteUser + openContextMenuWithOption(messageText, expectedOption) + assertTrue(expectedOption.isDisplayed()) + return this +} + +/** + * Opens the message menu of the message with [messageText] and asserts the block option it offers + * for the author. The sample has no blocked users list, so the option label is the only place the + * block state is visible. + */ +fun UserRobot.assertBlockMessageAuthorOption(messageText: String, isAuthorBlocked: Boolean): UserRobot { + val expectedOption = if (isAuthorBlocked) ContextMenu.unblock else ContextMenu.block + openContextMenuWithOption(messageText, expectedOption) + assertTrue(expectedOption.isDisplayed()) + return this +} diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelActionsTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelActionsTests.kt index 04b8092100f..29c6426f4c0 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelActionsTests.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelActionsTests.kt @@ -17,6 +17,7 @@ package io.getstream.chat.android.compose.tests import io.getstream.chat.android.compose.robots.assertChannelActionsSheetForGroupChannel +import io.getstream.chat.android.compose.robots.assertChannelIsMuted import io.getstream.chat.android.compose.robots.assertChannelListIsEmpty import io.getstream.chat.android.compose.robots.assertGroupChannelInfoScreen import io.getstream.chat.android.compose.sample.ui.InitTestActivity @@ -110,4 +111,42 @@ class ChannelActionsTests : StreamTestCase() { userRobot.assertChannelListIsEmpty() } } + + @AllureId("11560") + @Test + fun test_userMutesChannelFromTheSwipeAction() { + step("GIVEN user logs in") { + userRobot.login().waitForChannelListToLoad() + } + step("WHEN user swipes the channel and taps on the mute action") { + userRobot + .swipeChannel() + .tapOnMuteSwipeAction() + } + step("THEN the channel shows the muted icon") { + userRobot.assertChannelIsMuted(isMuted = true) + } + } + + @AllureId("11567") + @Test + fun test_userUnmutesChannelFromTheSwipeAction() { + step("GIVEN user logs in") { + userRobot.login().waitForChannelListToLoad() + } + step("AND user mutes the channel from the swipe action") { + userRobot + .swipeChannel() + .tapOnMuteSwipeAction() + .assertChannelIsMuted(isMuted = true) + } + step("WHEN user swipes the channel and taps on the unmute action") { + userRobot + .swipeChannel() + .tapOnUnmuteSwipeAction() + } + step("THEN the channel shows no muted icon") { + userRobot.assertChannelIsMuted(isMuted = false) + } + } } diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ModerationTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ModerationTests.kt index 9bc5d76b905..30a9b5b4ceb 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ModerationTests.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ModerationTests.kt @@ -16,16 +16,18 @@ package io.getstream.chat.android.compose.tests +import io.getstream.chat.android.compose.robots.assertBlockMessageAuthorOption import io.getstream.chat.android.compose.robots.assertFlagMessageDialog import io.getstream.chat.android.compose.robots.assertMessage +import io.getstream.chat.android.compose.robots.assertMuteMessageAuthorOption import io.getstream.chat.android.compose.sample.ui.InitTestActivity import io.qameta.allure.kotlin.Allure.step import io.qameta.allure.kotlin.AllureId import org.junit.Test /** - * Covers flagging another user's message from the message menu. The option is absent on the user's - * own messages. + * Covers the moderation options the message menu offers for another user's message: flagging the + * message, and muting or blocking its author. The options are absent on the user's own messages. */ class ModerationTests : StreamTestCase() { @@ -56,4 +58,78 @@ class ModerationTests : StreamTestCase() { .assertMessage(sampleText) } } + + @AllureId("11563") + @Test + fun test_userMutesMessageAuthor() { + step("GIVEN user opens the channel") { + userRobot.login().openChannel() + } + step("AND participant sends the message") { + participantRobot.sendMessage(sampleText) + } + step("WHEN user mutes the message author") { + userRobot.muteMessageAuthor(sampleText) + } + step("THEN the message menu offers to unmute the author") { + userRobot.assertMuteMessageAuthorOption(sampleText, isAuthorMuted = true) + } + } + + @AllureId("11566") + @Test + fun test_userUnmutesMessageAuthor() { + step("GIVEN user opens the channel") { + userRobot.login().openChannel() + } + step("AND participant sends the message") { + participantRobot.sendMessage(sampleText) + } + step("AND user mutes the message author") { + userRobot.muteMessageAuthor(sampleText) + } + step("WHEN user unmutes the message author") { + userRobot.unmuteMessageAuthor(sampleText) + } + step("THEN the message menu offers to mute the author again") { + userRobot.assertMuteMessageAuthorOption(sampleText, isAuthorMuted = false) + } + } + + @AllureId("6071") + @Test + fun test_userBlocksMessageAuthor() { + step("GIVEN user opens the channel") { + userRobot.login().openChannel() + } + step("AND participant sends the message") { + participantRobot.sendMessage(sampleText) + } + step("WHEN user blocks the message author") { + userRobot.blockMessageAuthor(sampleText) + } + step("THEN the message menu offers to unblock the author") { + userRobot.assertBlockMessageAuthorOption(sampleText, isAuthorBlocked = true) + } + } + + @AllureId("11575") + @Test + fun test_userUnblocksMessageAuthor() { + step("GIVEN user opens the channel") { + userRobot.login().openChannel() + } + step("AND participant sends the message") { + participantRobot.sendMessage(sampleText) + } + step("AND user blocks the message author") { + userRobot.blockMessageAuthor(sampleText) + } + step("WHEN user unblocks the message author") { + userRobot.unblockMessageAuthor(sampleText) + } + step("THEN the message menu offers to block the author again") { + userRobot.assertBlockMessageAuthorOption(sampleText, isAuthorBlocked = false) + } + } } From b42eaa41c0025091b9ecf08456245e17756dc797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Mion?= Date: Wed, 19 Aug 2026 15:04:19 +0100 Subject: [PATCH 2/5] compose-sample: Add a channel search mode flag A switch on the custom login screen makes the channel list header search for channels instead of messages. The e2e flavor assigns it from a CHANNEL_SEARCH launch extra on every start. --- .../android/compose/sample/ui/StartupActivity.kt | 2 ++ .../android/compose/sample/data/CustomSettings.kt | 2 ++ .../sample/feature/channel/list/ChannelsActivity.kt | 2 +- .../android/compose/sample/ui/chats/ChatsActivity.kt | 2 +- .../compose/sample/ui/login/CustomLoginActivity.kt | 12 ++++++++++++ .../src/main/res/values/strings.xml | 2 ++ 6 files changed, 20 insertions(+), 2 deletions(-) diff --git a/stream-chat-android-compose-sample/src/e2e/java/io/getstream/chat/android/compose/sample/ui/StartupActivity.kt b/stream-chat-android-compose-sample/src/e2e/java/io/getstream/chat/android/compose/sample/ui/StartupActivity.kt index bf918943564..3231fca745c 100644 --- a/stream-chat-android-compose-sample/src/e2e/java/io/getstream/chat/android/compose/sample/ui/StartupActivity.kt +++ b/stream-chat-android-compose-sample/src/e2e/java/io/getstream/chat/android/compose/sample/ui/StartupActivity.kt @@ -47,6 +47,8 @@ class StartupActivity : AppCompatActivity() { ChatClient.instance().disconnect(flushPersistence = true).await() ChatHelper.initializeSdk(applicationContext, PredefinedUserCredentials.API_KEY, baseUrl) customSettings().isComposerLinkPreviewEnabled = true + // Always assigned, so a flag left by a previous test cannot leak into this one. + customSettings().isChannelSearchEnabled = intent.getBooleanExtra("CHANNEL_SEARCH", false) } val initTestActivity = intent.getSerializableExtra("InitTestActivity") as? InitTestActivity diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/data/CustomSettings.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/data/CustomSettings.kt index c27561ebfc6..d1b4752cda6 100644 --- a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/data/CustomSettings.kt +++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/data/CustomSettings.kt @@ -39,6 +39,7 @@ class CustomSettings(private val context: Context) { var isSystemAttachmentPickerEnabled: Boolean by booleanPref(SystemAttachmentPicker) var isLocationSharingEnabled: Boolean by booleanPref(LocationSharing) var isLocalUnreadCountEnabled: Boolean by booleanPref(LocalUnreadCount) + var isChannelSearchEnabled: Boolean by booleanPref(ChannelSearch) private fun booleanPref(key: String, default: Boolean = false) = object : ReadWriteProperty { @@ -57,5 +58,6 @@ private const val ComposerFloatingStyle = "composer_floating_style" private const val SystemAttachmentPicker = "system_attachment_picker" private const val LocationSharing = "location_sharing" private const val LocalUnreadCount = "local_unread_count" +private const val ChannelSearch = "channel_search" fun Context.customSettings(): CustomSettings = CustomSettings(this) diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt index 0db23e4b61b..f79d3c2637a 100644 --- a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt +++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/feature/channel/list/ChannelsActivity.kt @@ -215,7 +215,7 @@ class ChannelsActivity : ComponentActivity() { ChannelsScreen( viewModelFactory = channelsViewModelFactory, isShowingHeader = true, - searchMode = SearchMode.Messages, + searchMode = if (settings.isChannelSearchEnabled) SearchMode.Channels else SearchMode.Messages, onChannelClick = ::openMessages, onSearchMessageItemClick = ::openMessages, onBackPressed = ::finish, diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt index c79296f6c75..02dc2499e89 100644 --- a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt +++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/chats/ChatsActivity.kt @@ -183,7 +183,7 @@ class ChatsActivity : ComponentActivity() { } }, title = stringResource(id = R.string.app_name), - searchMode = SearchMode.Messages, + searchMode = if (settings.isChannelSearchEnabled) SearchMode.Channels else SearchMode.Messages, listContentMode = listContentMode, onBackPress = ::finish, onListTopBarAvatarClick = { diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/login/CustomLoginActivity.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/login/CustomLoginActivity.kt index 8637fe73634..d50dd478cfc 100644 --- a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/login/CustomLoginActivity.kt +++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/login/CustomLoginActivity.kt @@ -144,6 +144,9 @@ class CustomLoginActivity : AppCompatActivity() { var isLocalUnreadCountEnabled by remember { mutableStateOf(settings.isLocalUnreadCountEnabled) } + var isChannelSearchEnabled by remember { + mutableStateOf(settings.isChannelSearchEnabled) + } val isLoginButtonEnabled = apiKeyText.isNotEmpty() && userIdText.isNotEmpty() && @@ -219,6 +222,15 @@ class CustomLoginActivity : AppCompatActivity() { settings.isLocalUnreadCountEnabled = it }, ), + FeatureFlag( + label = stringResource(R.string.custom_login_flag_channel_search_label), + description = stringResource(R.string.custom_login_flag_channel_search_description), + value = isChannelSearchEnabled, + onValueChange = { + isChannelSearchEnabled = it + settings.isChannelSearchEnabled = it + }, + ), ) CustomLoginInputField( diff --git a/stream-chat-android-compose-sample/src/main/res/values/strings.xml b/stream-chat-android-compose-sample/src/main/res/values/strings.xml index a31921dcedd..c077d6f75c0 100644 --- a/stream-chat-android-compose-sample/src/main/res/values/strings.xml +++ b/stream-chat-android-compose-sample/src/main/res/values/strings.xml @@ -47,6 +47,8 @@ Show the Pin/Unpin Chat action in the channel options menu Local unread count Track the unread count locally for channels with read events disabled. Also shows livestream channels in the channel list + Channel search + Search for channels instead of messages from the channel list header Pinned Messages From 18026f7de27f7110e54e4c1e494986f729137de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Mion?= Date: Wed, 19 Aug 2026 15:04:19 +0100 Subject: [PATCH 3/5] e2e: Cover channel search and blocking in a direct message channel The mock can now seed named channels and a two-member direct message channel, searching by channel name returns only the match, and blocking the participant hides the direct message channel until the unblock. --- .../android/compose/pages/ChannelListPage.kt | 4 ++ .../chat/android/compose/robots/UserRobot.kt | 11 ++++ .../robots/UserRobotChannelListAsserts.kt | 15 +++++ .../compose/tests/ChannelSearchTests.kt | 59 ++++++++++++++++++ .../android/compose/tests/ModerationTests.kt | 61 +++++++++++++++++++ .../chat/android/compose/tests/SearchTests.kt | 4 +- .../android/compose/tests/StreamTestCase.kt | 4 ++ .../android/e2e/test/robots/BackendRobot.kt | 10 +++ 8 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelSearchTests.kt diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/ChannelListPage.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/ChannelListPage.kt index fd2ff36a551..fba00a06043 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/ChannelListPage.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/ChannelListPage.kt @@ -62,6 +62,10 @@ class ChannelListPage { companion object { val avatar = By.res("Stream_ChannelAvatar") val name = By.res("Stream_ChannelName") + + // A fresh selector per call: BySelector chaining mutates the instance, + // so narrowing the shared `name` selector would poison later uses. + fun name(text: String): BySelector = By.res("Stream_ChannelName").text(text) val messagePreview = By.res("Stream_MessagePreview") val deliveryStatusIsRead = Message.deliveryStatusIsRead val deliveryStatusIsPending = Message.deliveryStatusIsPending 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 8586c838086..99fb374dc1f 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 @@ -88,6 +88,11 @@ class UserRobot { return this } + fun openChannel(channelName: String): UserRobot { + ChannelListPage.ChannelList.Channel.name(channelName).waitToAppearAndClick() + return this + } + fun openContextMenu(messageCellIndex: Int = 0): UserRobot { val messages = MessageList.messages.waitToAppearBottomUp() val message = if (messages.size < messageCellIndex + 1) messages.last() else messages[messageCellIndex] @@ -499,6 +504,12 @@ class UserRobot { return this } + /** Same input as [searchForMessage]; what it searches depends on the app's search mode. */ + fun searchForChannel(text: String): UserRobot { + ChannelListPage.Header.searchField.waitToAppear().typeText(text) + return this + } + /** * Taps the first search result. The result rows carry no test tag of their own, so the tap * lands on the message preview inside the row, which the row handles. diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotChannelListAsserts.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotChannelListAsserts.kt index 61f6368afec..aad8b3e0545 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotChannelListAsserts.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotChannelListAsserts.kt @@ -25,6 +25,7 @@ import io.getstream.chat.android.e2e.test.robots.ParticipantRobot import io.getstream.chat.android.e2e.test.uiautomator.appContext import io.getstream.chat.android.e2e.test.uiautomator.isDisplayed import io.getstream.chat.android.e2e.test.uiautomator.waitDisplayed +import io.getstream.chat.android.e2e.test.uiautomator.waitForCount import io.getstream.chat.android.e2e.test.uiautomator.waitForText import io.getstream.chat.android.e2e.test.uiautomator.waitToDisappear import org.junit.Assert.assertEquals @@ -94,6 +95,20 @@ fun UserRobot.assertChannelListIsEmpty(): UserRobot { return this } +fun UserRobot.assertChannelCount(count: Int): UserRobot { + assertEquals(count, ChannelList.channels.waitForCount(count).size) + return this +} + +fun UserRobot.assertChannelWithName(name: String, isDisplayed: Boolean = true): UserRobot { + if (isDisplayed) { + assertTrue(Channel.name(name).waitDisplayed()) + } else { + assertFalse(Channel.name(name).waitToDisappear().isDisplayed()) + } + return this +} + fun UserRobot.assertMessagePreviewTimestamp(isDisplayed: Boolean = true): UserRobot { if (isDisplayed) { assertTrue(Channel.timestamp.waitDisplayed()) diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelSearchTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelSearchTests.kt new file mode 100644 index 00000000000..c3165bc206c --- /dev/null +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelSearchTests.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014-2026 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.assertChannelCount +import io.getstream.chat.android.compose.robots.assertChannelWithName +import io.getstream.chat.android.compose.sample.ui.InitTestActivity +import io.qameta.allure.kotlin.Allure.step +import io.qameta.allure.kotlin.AllureId +import org.junit.Test + +/** + * Covers the channel search wired into the channel list header. It needs the app started with the + * channel search flag, which configures the channel list with `SearchMode.Channels`. Message + * search, the default mode, is covered by [SearchTests]. + */ +class ChannelSearchTests : StreamTestCase() { + + override fun initTestActivity() = InitTestActivity.UserLogin + override var useChannelSearch = true + + private val channelName = "Blue Team" + private val searchQuery = "Blue" + + @AllureId("5934") + @Test + fun test_userSearchesForChannel() { + step("GIVEN channels exist, one with a searchable name") { + backendRobot.generateChannels(channelsCount = 3, channelNames = listOf(channelName)) + } + step("AND user logs in") { + userRobot + .login() + .waitForChannelListToLoad() + } + step("WHEN user searches for the channel by its name") { + userRobot.searchForChannel(searchQuery) + } + step("THEN only the matching channel is shown") { + userRobot + .assertChannelCount(1) + .assertChannelWithName(channelName) + } + } +} diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ModerationTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ModerationTests.kt index 30a9b5b4ceb..d2cdd798dba 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ModerationTests.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ModerationTests.kt @@ -17,10 +17,13 @@ package io.getstream.chat.android.compose.tests import io.getstream.chat.android.compose.robots.assertBlockMessageAuthorOption +import io.getstream.chat.android.compose.robots.assertChannelCount +import io.getstream.chat.android.compose.robots.assertChannelWithName import io.getstream.chat.android.compose.robots.assertFlagMessageDialog import io.getstream.chat.android.compose.robots.assertMessage import io.getstream.chat.android.compose.robots.assertMuteMessageAuthorOption import io.getstream.chat.android.compose.sample.ui.InitTestActivity +import io.getstream.chat.android.e2e.test.robots.ParticipantRobot import io.qameta.allure.kotlin.Allure.step import io.qameta.allure.kotlin.AllureId import org.junit.Test @@ -34,6 +37,11 @@ class ModerationTests : StreamTestCase() { override fun initTestActivity() = InitTestActivity.UserLogin private val sampleText = "Test" + // Seeded messages alternate authors starting with the user, so with two seeded + // messages the one with text "2" belongs to the participant. + private val participantMessageText = "2" + private val groupChannelName = "1" + @AllureId("11572") @Test fun test_userFlagsMessage() { @@ -132,4 +140,57 @@ class ModerationTests : StreamTestCase() { userRobot.assertBlockMessageAuthorOption(sampleText, isAuthorBlocked = false) } } + + @AllureId("6070") + @Test + fun test_userBlocksUserInDirectMessageChannel() { + step("GIVEN a direct message channel with the participant exists") { + backendRobot.generateChannels(channelsCount = 1, messagesCount = 2, withDirectMessageChannel = true) + } + step("AND user opens the direct message channel") { + userRobot.login().openChannel(ParticipantRobot.name) + } + step("WHEN user blocks the participant") { + userRobot.blockMessageAuthor(participantMessageText) + } + step("THEN the message menu offers to unblock the participant") { + userRobot.assertBlockMessageAuthorOption(participantMessageText, isAuthorBlocked = true) + } + } + + @AllureId("6072") + @Test + fun test_directMessageChannelDisappears_whenUserBlocksParticipant() { + step("GIVEN a direct message channel with the participant exists") { + backendRobot.generateChannels(channelsCount = 1, messagesCount = 2, withDirectMessageChannel = true) + } + step("AND user sees it in the channel list") { + userRobot + .login() + .assertChannelCount(2) + .assertChannelWithName(ParticipantRobot.name) + } + step("WHEN user blocks the participant in the direct message channel") { + userRobot + .openChannel(ParticipantRobot.name) + .blockMessageAuthor(participantMessageText) + .moveToChannelListFromMessageList() + } + step("THEN the direct message channel disappears from the channel list") { + userRobot + .assertChannelCount(1) + .assertChannelWithName(ParticipantRobot.name, isDisplayed = false) + } + step("WHEN user unblocks the participant from the group channel") { + userRobot + .openChannel(groupChannelName) + .unblockMessageAuthor(participantMessageText) + .moveToChannelListFromMessageList() + } + step("THEN the direct message channel is shown in the channel list again") { + userRobot + .assertChannelCount(2) + .assertChannelWithName(ParticipantRobot.name) + } + } } diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/SearchTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/SearchTests.kt index 15b7dfb7a3f..592a03ae6c8 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/SearchTests.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/SearchTests.kt @@ -24,8 +24,8 @@ import io.qameta.allure.kotlin.AllureId import org.junit.Test /** - * Covers the message search wired into the channel list header. The sample configures the channel - * list with `SearchMode.Messages` only, so channel search is not reachable. + * Covers the message search wired into the channel list header, the sample's default search mode. + * Channel search, behind the channel search flag, is covered by [ChannelSearchTests]. */ class SearchTests : StreamTestCase() { 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 3c75881af3d..13b77ea10ec 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 @@ -42,6 +42,9 @@ abstract class StreamTestCase { lateinit var mockServer: MockServer open var useMockServer = true + + /** Makes the channel list header search for channels instead of messages. */ + open var useChannelSearch = false val userRobot = UserRobot() lateinit var backendRobot: BackendRobot lateinit var participantRobot: ParticipantRobot @@ -93,6 +96,7 @@ abstract class StreamTestCase { testContext.packageManager.getLaunchIntentForPackage(packageName)?.let { it.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) it.putExtra("InitTestActivity", initTestActivity()) + it.putExtra("CHANNEL_SEARCH", useChannelSearch) if (useMockServer) { it.putExtra("BASE_URL", mockServer.url) } diff --git a/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/robots/BackendRobot.kt b/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/robots/BackendRobot.kt index df5cad2d1da..2772f572505 100644 --- a/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/robots/BackendRobot.kt +++ b/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/robots/BackendRobot.kt @@ -32,14 +32,24 @@ public class BackendRobot( repliesCount: Int = 0, messagesText: String? = null, repliesText: String? = null, + channelNames: List = emptyList(), + withDirectMessageChannel: Boolean = false, ): BackendRobot { waitForMockServerToStart() val messagesTextQueryParam = if (messagesText != null) "messages_text=$messagesText&" else "" val repliesTextQueryParam = if (repliesText != null) "replies_text=$repliesText&" else "" + val channelNamesQueryParam = if (channelNames.isNotEmpty()) { + "channel_names=${channelNames.joinToString(",")}&" + } else { + "" + } + val dmQueryParam = if (withDirectMessageChannel) "dm=true&" else "" mockServer.postRequest( "mock?" + messagesTextQueryParam + repliesTextQueryParam + + channelNamesQueryParam + + dmQueryParam + "channels=$channelsCount&" + "messages=$messagesCount&" + "replies=$repliesCount", From d2c2ca458b7640811266b62e94062a99e93eaba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Mion?= Date: Wed, 19 Aug 2026 16:02:57 +0100 Subject: [PATCH 4/5] e2e: Tighten the moderation asserts and the channel search plumbing The mute and block option asserts also check that the opposite option is absent, the CHANNEL_SEARCH extra applies on every harness launch so the flag cannot leak into the tests that run without a mock server, and channel names are URL-encoded before entering the /mock query. --- .../android/compose/robots/UserRobotMessageListAsserts.kt | 4 ++++ .../chat/android/compose/sample/ui/StartupActivity.kt | 7 ++++++- .../getstream/chat/android/e2e/test/robots/BackendRobot.kt | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt index 9651cb0dee5..014b7763bfe 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt @@ -561,8 +561,10 @@ fun UserRobot.assertFlagMessageDialog(isDisplayed: Boolean): UserRobot { */ fun UserRobot.assertMuteMessageAuthorOption(messageText: String, isAuthorMuted: Boolean): UserRobot { val expectedOption = if (isAuthorMuted) ContextMenu.unmuteUser else ContextMenu.muteUser + val oppositeOption = if (isAuthorMuted) ContextMenu.muteUser else ContextMenu.unmuteUser openContextMenuWithOption(messageText, expectedOption) assertTrue(expectedOption.isDisplayed()) + assertFalse(oppositeOption.isDisplayed()) return this } @@ -573,7 +575,9 @@ fun UserRobot.assertMuteMessageAuthorOption(messageText: String, isAuthorMuted: */ fun UserRobot.assertBlockMessageAuthorOption(messageText: String, isAuthorBlocked: Boolean): UserRobot { val expectedOption = if (isAuthorBlocked) ContextMenu.unblock else ContextMenu.block + val oppositeOption = if (isAuthorBlocked) ContextMenu.block else ContextMenu.unblock openContextMenuWithOption(messageText, expectedOption) assertTrue(expectedOption.isDisplayed()) + assertFalse(oppositeOption.isDisplayed()) return this } diff --git a/stream-chat-android-compose-sample/src/e2e/java/io/getstream/chat/android/compose/sample/ui/StartupActivity.kt b/stream-chat-android-compose-sample/src/e2e/java/io/getstream/chat/android/compose/sample/ui/StartupActivity.kt index 3231fca745c..5703db1ca49 100644 --- a/stream-chat-android-compose-sample/src/e2e/java/io/getstream/chat/android/compose/sample/ui/StartupActivity.kt +++ b/stream-chat-android-compose-sample/src/e2e/java/io/getstream/chat/android/compose/sample/ui/StartupActivity.kt @@ -47,7 +47,12 @@ class StartupActivity : AppCompatActivity() { ChatClient.instance().disconnect(flushPersistence = true).await() ChatHelper.initializeSdk(applicationContext, PredefinedUserCredentials.API_KEY, baseUrl) customSettings().isComposerLinkPreviewEnabled = true - // Always assigned, so a flag left by a previous test cannot leak into this one. + } + + // Assigned on every harness launch, with or without a mock server, so a flag left by + // a previous test cannot leak into the next one. A notification launch carries no + // extra and keeps the persisted value. + if (intent.hasExtra("CHANNEL_SEARCH")) { customSettings().isChannelSearchEnabled = intent.getBooleanExtra("CHANNEL_SEARCH", false) } diff --git a/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/robots/BackendRobot.kt b/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/robots/BackendRobot.kt index 2772f572505..0d4d5f19231 100644 --- a/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/robots/BackendRobot.kt +++ b/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/robots/BackendRobot.kt @@ -21,6 +21,7 @@ import junit.framework.TestCase.fail import org.json.JSONObject import java.net.HttpURLConnection import java.net.URL +import java.net.URLEncoder public class BackendRobot( private val mockServer: MockServer, @@ -39,7 +40,7 @@ public class BackendRobot( val messagesTextQueryParam = if (messagesText != null) "messages_text=$messagesText&" else "" val repliesTextQueryParam = if (repliesText != null) "replies_text=$repliesText&" else "" val channelNamesQueryParam = if (channelNames.isNotEmpty()) { - "channel_names=${channelNames.joinToString(",")}&" + "channel_names=${URLEncoder.encode(channelNames.joinToString(","), "UTF-8")}&" } else { "" } From 36da6dccd88d0ca7f8862339a1c8765ca8a5a05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Mion?= Date: Thu, 20 Aug 2026 13:39:58 +0100 Subject: [PATCH 5/5] e2e: Simplify the moderation menu helpers and harden the search test The message menu rebuilds its options from the collected user state while it stays open, so the helper opens it once and waits for the option instead of reopening it. The mute and block asserts close the menu when they are done. The channel search test asserts the pre-search count so the 3 to 1 transition is the assertion. The channel name selectors are getters, and the two identical search functions are one, since the app search mode decides what is searched. --- .../android/compose/pages/ChannelListPage.kt | 6 ++-- .../chat/android/compose/robots/UserRobot.kt | 29 +++++-------------- .../robots/UserRobotMessageListAsserts.kt | 2 ++ .../compose/tests/ChannelSearchTests.kt | 3 +- .../chat/android/compose/tests/SearchTests.kt | 4 +-- 5 files changed, 16 insertions(+), 28 deletions(-) diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/ChannelListPage.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/ChannelListPage.kt index fba00a06043..c754fb45d39 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/ChannelListPage.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/ChannelListPage.kt @@ -61,11 +61,9 @@ class ChannelListPage { companion object { val avatar = By.res("Stream_ChannelAvatar") - val name = By.res("Stream_ChannelName") + val name get() = By.res("Stream_ChannelName") - // A fresh selector per call: BySelector chaining mutates the instance, - // so narrowing the shared `name` selector would poison later uses. - fun name(text: String): BySelector = By.res("Stream_ChannelName").text(text) + fun name(text: String): BySelector = name.text(text) val messagePreview = By.res("Stream_MessagePreview") val deliveryStatusIsRead = Message.deliveryStatusIsRead val deliveryStatusIsPending = Message.deliveryStatusIsPending 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 b10db0624a7..76ec0b2b274 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 @@ -210,20 +210,13 @@ class UserRobot { } /** - * Opens the message menu of the message with [text], reopening it while [option] is missing. - * The menu builds its options when it opens and keeps them while it stays open, so an option - * that flips because of a moderation action shows up only on a later open. + * Opens the message menu of the message with [text] and waits for [option] to show. The open + * menu rebuilds its options when the own user state lands, so a moderation option that flips + * appears without reopening the menu. */ internal fun openContextMenuWithOption(text: String, option: BySelector): UserRobot { - repeat(contextMenuOpenAttempts) { attempt -> - openContextMenu(text) - if (option.waitDisplayed(timeOutMillis = 5.seconds)) { - return this - } - if (attempt < contextMenuOpenAttempts - 1) { - pressBack() - } - } + openContextMenu(text) + option.waitDisplayed(timeOutMillis = 15.seconds) return this } @@ -500,13 +493,9 @@ class UserRobot { return this } - fun searchForMessage(text: String): UserRobot { - ChannelListPage.Header.searchField.waitToAppear().typeText(text) - return this - } - - /** Same input as [searchForMessage]; what it searches depends on the app's search mode. */ - fun searchForChannel(text: String): UserRobot { + /** Types into the channel list header search input; the app's search mode decides whether + * messages or channels are searched. */ + fun search(text: String): UserRobot { ChannelListPage.Header.searchField.waitToAppear().typeText(text) return this } @@ -701,5 +690,3 @@ class UserRobot { return this } } - -private const val contextMenuOpenAttempts = 3 diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt index 014b7763bfe..d6252a095f7 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt @@ -565,6 +565,7 @@ fun UserRobot.assertMuteMessageAuthorOption(messageText: String, isAuthorMuted: openContextMenuWithOption(messageText, expectedOption) assertTrue(expectedOption.isDisplayed()) assertFalse(oppositeOption.isDisplayed()) + pressBack() return this } @@ -579,5 +580,6 @@ fun UserRobot.assertBlockMessageAuthorOption(messageText: String, isAuthorBlocke openContextMenuWithOption(messageText, expectedOption) assertTrue(expectedOption.isDisplayed()) assertFalse(oppositeOption.isDisplayed()) + pressBack() return this } diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelSearchTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelSearchTests.kt index c3165bc206c..4e2c0335380 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelSearchTests.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelSearchTests.kt @@ -46,9 +46,10 @@ class ChannelSearchTests : StreamTestCase() { userRobot .login() .waitForChannelListToLoad() + .assertChannelCount(3) } step("WHEN user searches for the channel by its name") { - userRobot.searchForChannel(searchQuery) + userRobot.search(searchQuery) } step("THEN only the matching channel is shown") { userRobot diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/SearchTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/SearchTests.kt index 592a03ae6c8..a5df737da60 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/SearchTests.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/SearchTests.kt @@ -44,7 +44,7 @@ class SearchTests : StreamTestCase() { step("WHEN user searches for the message on the channel list") { userRobot .moveToChannelListFromMessageList() - .searchForMessage(sampleText) + .search(sampleText) } step("THEN the message is shown in the search results") { userRobot.assertMessageInChannelPreview(sampleText, fromCurrentUser = false) @@ -63,7 +63,7 @@ class SearchTests : StreamTestCase() { step("AND user searches for the message on the channel list") { userRobot .moveToChannelListFromMessageList() - .searchForMessage(sampleText) + .search(sampleText) .assertMessageInChannelPreview(sampleText, fromCurrentUser = false) } step("WHEN user taps on the search result") {