Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ open class MessageListPage {
val cooldownIndicator get() = By.res("Stream_ComposerCooldownIndicator")
val saveButton get() = By.res("Stream_ComposerSaveButton")
val recordAudioButton get() = By.res("Stream_ComposerAudioRecordingButton")
val commandsButton get() = By.res("Stream_ComposerCommandsButton")
val commandSuggestionList get() = By.res("Stream_CommandSuggestionList")
val commandSuggestionListTitle get() = By.res("Stream_CommandSuggestionListTitle")
val userSuggestion get() = By.res("Stream_SuggestionItem")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import io.getstream.chat.android.e2e.test.mockserver.ReactionType
import io.getstream.chat.android.e2e.test.robots.ParticipantRobot
import io.getstream.chat.android.e2e.test.uiautomator.defaultTimeout
import io.getstream.chat.android.e2e.test.uiautomator.device
import io.getstream.chat.android.e2e.test.uiautomator.findObject
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
Expand Down Expand Up @@ -120,6 +119,27 @@ class UserRobot {
return this
}

/**
* Taps whichever confirm button the composer is showing. Selecting a command suggestion
* activates command mode, where the trailing button is the save button instead of the
* send button; with the sample's configuration both build the same message.
*/
private fun tapOnComposerConfirmButton(): UserRobot {
val endTime = System.currentTimeMillis() + defaultTimeout
while (System.currentTimeMillis() < endTime) {
Composer.sendButton.findObjects().firstOrNull()?.let {
it.click()
return this
}
Composer.saveButton.findObjects().firstOrNull()?.let {
it.click()
return this
}
Thread.sleep(50)
}
error("Neither the send nor the save composer button appeared within ${defaultTimeout}ms")
}

fun tapOnLinkPreviewCancelButton(): UserRobot {
Composer.linkPreviewCancelButton.waitToAppear().click()
return this
Expand Down Expand Up @@ -283,7 +303,9 @@ class UserRobot {
}

fun openComposerCommands(): UserRobot {
Composer.commandsButton.waitToAppear().click()
// The composer redesign removed the dedicated commands button; typing '/' in the
// input field opens the command suggestion list.
typeText("/")
return this
}

Expand All @@ -296,10 +318,11 @@ class UserRobot {
val giphyMessageText = "G" // any message text will result in sending a giphy
if (useComposerCommand) {
openComposerCommands()
// Selecting the suggestion prefills '/giphy '; typeText replaces the whole input,
// so the command prefix is set together with the message text.
Composer.giphyButton.waitToAppear().click()
Composer.inputField.findObject().click()
device.typeText(giphyMessageText)
Composer.sendButton.findObject().click()
typeText("/giphy $giphyMessageText")
tapOnComposerConfirmButton()
} else {
sendMessage("/giphy $giphyMessageText")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ package io.getstream.chat.android.compose.robots
import io.getstream.chat.android.compose.pages.ChannelListPage.ChannelList.Channel
import io.getstream.chat.android.e2e.test.robots.ParticipantRobot
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.waitForText
import io.getstream.chat.android.e2e.test.uiautomator.waitToAppear
import io.getstream.chat.android.e2e.test.uiautomator.waitToDisappear
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
Expand All @@ -46,7 +46,7 @@ fun UserRobot.assertMessageInChannelPreview(text: String, fromCurrentUser: Boole

fun UserRobot.assertMessagePreviewTimestamp(isDisplayed: Boolean = true): UserRobot {
if (isDisplayed) {
assertTrue(Channel.timestamp.waitToAppear().isDisplayed())
assertTrue(Channel.timestamp.waitDisplayed())
} else {
assertFalse(Channel.timestamp.waitToDisappear().isDisplayed())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import io.getstream.chat.android.e2e.test.mockserver.MessageDeliveryStatus
import io.getstream.chat.android.e2e.test.mockserver.ReactionType
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.defaultTimeout
import io.getstream.chat.android.e2e.test.uiautomator.device
import io.getstream.chat.android.e2e.test.uiautomator.findObject
import io.getstream.chat.android.e2e.test.uiautomator.findObjects
Expand All @@ -36,7 +37,7 @@ import io.getstream.chat.android.e2e.test.uiautomator.isDisplayed
import io.getstream.chat.android.e2e.test.uiautomator.isEnabled
import io.getstream.chat.android.e2e.test.uiautomator.retryOnStaleObjectException
import io.getstream.chat.android.e2e.test.uiautomator.seconds
import io.getstream.chat.android.e2e.test.uiautomator.wait
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.waitToAppear
Expand All @@ -51,11 +52,11 @@ import org.junit.Assert.assertTrue
* Asserts the selector's visibility with a bounded wait: waits for it to appear when
* [isDisplayed] is `true`, or to disappear when `false`, then asserts the final state.
*/
private fun assertVisibility(selector: BySelector, isDisplayed: Boolean) {
private fun assertVisibility(selector: BySelector, isDisplayed: Boolean, timeOutMillis: Long = defaultTimeout) {
if (isDisplayed) {
assertTrue(selector.waitToAppear().isDisplayed())
assertTrue(selector.waitDisplayed(timeOutMillis))
} else {
assertFalse(selector.waitToDisappear().isDisplayed())
assertFalse(selector.waitToDisappear(timeOutMillis).isDisplayed())
}
}

Expand All @@ -67,7 +68,7 @@ fun UserRobot.assertMessage(
if (isDisplayed) {
val textLocator = (if (isClickable) Message.clickableText else Message.text)
.text(text)
assertTrue(textLocator.waitToAppear().isDisplayed())
assertTrue(textLocator.waitDisplayed())
assertTrue(Message.timestamp.isDisplayed())
} else {
MessageListPage.MessageList.messages.findObjects().forEach {
Expand All @@ -91,25 +92,25 @@ fun UserRobot.assertMessageTimestamps(count: Int): UserRobot {
fun UserRobot.assertMessageDeliveryStatus(status: MessageDeliveryStatus, count: Int? = null): UserRobot {
when (status) {
MessageDeliveryStatus.READ -> {
assertTrue(Message.deliveryStatusIsRead.wait(30.seconds).isDisplayed())
assertVisibility(Message.deliveryStatusIsRead, isDisplayed = true, timeOutMillis = 30.seconds)
if (count != null) {
assertEquals(count, Message.deliveryStatusIsRead.waitForCount(count).size)
}
}
MessageDeliveryStatus.PENDING -> {
assertTrue(Message.deliveryStatusIsPending.wait().isDisplayed())
assertVisibility(Message.deliveryStatusIsPending, isDisplayed = true)
if (count != null) {
assertEquals(count, Message.deliveryStatusIsPending.waitForCount(count).size)
}
}
MessageDeliveryStatus.SENT -> {
assertTrue(Message.deliveryStatusIsSent.wait().isDisplayed())
assertVisibility(Message.deliveryStatusIsSent, isDisplayed = true)
if (count != null) {
assertEquals(count, Message.deliveryStatusIsSent.waitForCount(count).size)
}
}
MessageDeliveryStatus.FAILED -> {
assertTrue(Message.deliveryStatusIsFailed.wait().isDisplayed())
assertVisibility(Message.deliveryStatusIsFailed, isDisplayed = true)
if (count != null) {
assertEquals(count, Message.deliveryStatusIsFailed.waitForCount(count).size)
}
Expand All @@ -124,11 +125,7 @@ fun UserRobot.assertMessageDeliveryStatus(status: MessageDeliveryStatus, count:
}

fun UserRobot.assertMessageFailedIcon(isDisplayed: Boolean): UserRobot {
if (isDisplayed) {
assertTrue(Message.deliveryStatusIsFailed.wait().isDisplayed())
} else {
assertFalse(Message.deliveryStatusIsFailed.waitToDisappear().isDisplayed())
}
assertVisibility(Message.deliveryStatusIsFailed, isDisplayed)
return this
}

Expand Down Expand Up @@ -214,7 +211,7 @@ fun UserRobot.assertAttachmentsMenu(isDisplayed: Boolean): UserRobot {

fun UserRobot.assertComposerCommandsMenu(isDisplayed: Boolean): UserRobot {
if (isDisplayed) {
assertTrue(Composer.commandSuggestionList.waitToAppear().isDisplayed())
assertTrue(Composer.commandSuggestionList.waitDisplayed())
assertTrue(Composer.commandSuggestionListTitle.isDisplayed())
} else {
assertFalse(Composer.commandSuggestionList.waitToDisappear().isDisplayed())
Expand Down Expand Up @@ -243,7 +240,7 @@ fun UserRobot.assertComposerText(expectedText: String): UserRobot {
}

fun UserRobot.assertCooldownIsShown(): UserRobot {
assertTrue(Composer.cooldownIndicator.waitToAppear().isDisplayed())
assertTrue(Composer.cooldownIndicator.waitDisplayed())
assertFalse(Composer.sendButton.isDisplayed())
return this
}
Expand All @@ -265,7 +262,7 @@ fun UserRobot.assertScrollToBottomButton(isDisplayed: Boolean): UserRobot {
}

fun UserRobot.assertThreadIsOpen(): UserRobot {
assertTrue(ThreadPage.ThreadList.alsoSendToChannelCheckbox.waitToAppear().isDisplayed())
assertTrue(ThreadPage.ThreadList.alsoSendToChannelCheckbox.waitDisplayed())
return this
}

Expand Down Expand Up @@ -300,9 +297,7 @@ fun UserRobot.assertAlsoInTheChannelLabelInThread(): UserRobot {

fun UserRobot.assertGiphyImage(isDisplayed: Boolean = true): UserRobot {
if (isDisplayed) {
device.retryOnStaleObjectException {
assertTrue(Message.giphy.waitToAppear().isDisplayed())
}
assertTrue(Message.giphy.waitDisplayed())
} else {
assertFalse(Message.giphy.waitToDisappear().isDisplayed())
}
Expand All @@ -311,7 +306,7 @@ fun UserRobot.assertGiphyImage(isDisplayed: Boolean = true): UserRobot {

fun UserRobot.assertGiphyButtons(areDisplayed: Boolean = true): UserRobot {
if (areDisplayed) {
assertTrue(Message.GiphyButtons.send.waitToAppear().isDisplayed())
assertTrue(Message.GiphyButtons.send.waitDisplayed())
assertTrue(Message.GiphyButtons.cancel.findObject().isDisplayed())
assertTrue(Message.GiphyButtons.shuffle.findObject().isDisplayed())
} else {
Expand Down Expand Up @@ -393,7 +388,7 @@ fun UserRobot.assertVideo(isDisplayed: Boolean, count: Int = 1): UserRobot {
if (isDisplayed) {
assertEquals(count, Message.video.waitForCount(count).size)
if (count != 1) {
assertTrue(Message.columnWithMultipleMediaAttachments.waitToAppear().isDisplayed())
assertTrue(Message.columnWithMultipleMediaAttachments.waitDisplayed())
}
} else {
assertFalse(Message.video.waitToDisappear().isDisplayed())
Expand Down Expand Up @@ -433,7 +428,7 @@ fun UserRobot.assertMediaAttachmentInPreview(isDisplayed: Boolean, count: Int =

fun UserRobot.assertFileAttachmentInPreview(isDisplayed: Boolean, count: Int = 1): UserRobot {
if (isDisplayed) {
assertTrue(Composer.fileName.waitToAppear().isDisplayed())
assertTrue(Composer.fileName.waitDisplayed())
assertTrue(Composer.fileSize.isDisplayed())
assertTrue(Composer.fileImage.isDisplayed())
assertTrue(Composer.attachmentCancelIcon.isDisplayed())
Expand All @@ -451,7 +446,7 @@ fun UserRobot.assertFileAttachmentInPreview(isDisplayed: Boolean, count: Int = 1

fun UserRobot.assertLinkPreviewInMessageList(isDisplayed: Boolean): UserRobot {
if (isDisplayed) {
assertTrue(Message.linkPreviewImage.waitToAppear().isDisplayed())
assertTrue(Message.linkPreviewImage.waitDisplayed())
assertTrue(Message.linkPreviewTitle.isDisplayed())
assertTrue(Message.linkPreviewDescription.isDisplayed())
} else {
Expand All @@ -464,7 +459,7 @@ fun UserRobot.assertLinkPreviewInMessageList(isDisplayed: Boolean): UserRobot {

fun UserRobot.assertLinkPreviewInComposer(isDisplayed: Boolean): UserRobot {
if (isDisplayed) {
assertTrue(Composer.linkPreviewImage.waitToAppear().isDisplayed())
assertTrue(Composer.linkPreviewImage.waitDisplayed())
assertTrue(Composer.linkPreviewTitle.isDisplayed())
assertTrue(Composer.linkPreviewDescription.isDisplayed())
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import io.getstream.chat.android.e2e.test.uiautomator.disableInternetConnection
import io.getstream.chat.android.e2e.test.uiautomator.enableInternetConnection
import io.getstream.chat.android.e2e.test.uiautomator.goToBackground
import io.getstream.chat.android.e2e.test.uiautomator.goToForeground
import io.getstream.chat.android.e2e.test.uiautomator.isDisplayed
import io.getstream.chat.android.e2e.test.uiautomator.seconds
import io.getstream.chat.android.e2e.test.uiautomator.waitDisplayed
import io.getstream.chat.android.e2e.test.uiautomator.waitToAppear
import io.qameta.allure.kotlin.Allure.step
import io.qameta.allure.kotlin.AllureId
Expand Down Expand Up @@ -238,12 +238,12 @@ class AuthTests : StreamTestCase() {
}

private fun UserRobot.assertConnectionStatus(): UserRobot {
assertTrue(JwtPage.statusConnected.waitToAppear(15.seconds).isDisplayed())
assertTrue(JwtPage.statusConnected.waitDisplayed(15.seconds))
return this
}

private fun UserRobot.assertTokenHasExpired(): UserRobot {
assertTrue(JwtPage.statusOffline.waitToAppear(15.seconds).isDisplayed())
assertTrue(JwtPage.statusOffline.waitDisplayed(15.seconds))
return this
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ class GiphyTests : StreamTestCase() {
}

@AllureId("5823")
@Ignore("https://linear.app/stream/issue/AND-1018")
@Test
fun test_userObservesAnimatedGiphy_afterAddingGiphyThroughComposerMenu() {
step("GIVEN user opens a channel") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ public class RetryRule(private val count: Int) : TestRule {

for (attempt in 1..count) {
val recordVideo = attempt > 1
val videoFilePath = "${Environment.getExternalStorageDirectory().absolutePath}/$testName.mp4"
// methodName, not displayName: the display name contains parentheses, which
// break when the recording commands go through the device shell.
val videoFilePath =
"${Environment.getExternalStorageDirectory().absolutePath}/${description.methodName}.mp4"
var recordingThread: Thread? = null
val startMillis = System.currentTimeMillis()
try {
Expand All @@ -63,17 +66,17 @@ public class RetryRule(private val count: Int) : TestRule {
recordingThread = startVideoRecording(videoFilePath)
}
base.evaluate()
recordingThread?.let { stopVideoRecording(videoFilePath, it) }
recordingThread?.let { stopRecordingSafely(testName, videoFilePath, it) }
return
} catch (t: Throwable) {
System.err.println("$testName: run #$attempt failed.")
caughtThrowable = t
databaseOperations.clearDatabases()
recordingThread?.let { stopVideoRecording(videoFilePath, it) }
val recordingStopped = recordingThread?.let { stopRecordingSafely(testName, videoFilePath, it) }
device.allureLogcat(name = "logcat_$attempt")
device.allureScreenshot(name = "screenshot_$attempt")
device.allureWindowHierarchy(name = "hierarchy_$attempt")
recordingThread?.let {
if (recordingStopped == true) {
device.allureScreenrecord(name = "record_$attempt", file = File(videoFilePath))
}
if (attempt < count) {
Expand Down Expand Up @@ -136,6 +139,16 @@ public class RetryRule(private val count: Int) : TestRule {
lifecycle.writeTestCase(attemptResult.uuid)
}

/**
* Stops the recording without failing the test: a recording infrastructure problem must
* not change the test result or skip the failure reporting. Returns whether the recording
* was stopped and its file is usable.
*/
private fun stopRecordingSafely(testName: String, videoFilePath: String, thread: Thread): Boolean =
runCatching { stopVideoRecording(videoFilePath, thread) }
.onFailure { System.err.println("$testName: stopping the screen recording failed: $it") }
.isSuccess

private fun startVideoRecording(remoteVideoPath: String): Thread {
return Thread {
device.executeShellCommand(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File

public fun UiDevice.typeText(text: String) {
executeShellCommand("input text '$text'")
}

public fun UiObject2.typeText(text: String): UiObject2 {
this.text = text
return this
Expand Down
Loading
Loading