Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ fun UserRobot.assertMessageInChannelPreview(text: String, fromCurrentUser: Boole
false -> "${ParticipantRobot.name}: $text"
null -> text
}
assertEquals(expectedPreview, Channel.messagePreview.waitToAppear().waitForText(expectedPreview).text.trimEnd())
assertEquals(
expectedPreview,
Channel.messagePreview.waitForText(expectedPreview, mustBeEqual = false).trimEnd(),
Comment thread
VelikovPetar marked this conversation as resolved.
)
return this
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,8 @@ fun UserRobot.assertMessageFailedIcon(isDisplayed: Boolean): UserRobot {

fun UserRobot.assertEditedMessage(text: String): UserRobot {
assertMessage(text)
assertEquals(
appContext.getString(R.string.stream_compose_message_list_footnote_edited),
Message.editedLabel.waitToAppear().text,
)
val expectedLabel = appContext.getString(R.string.stream_compose_message_list_footnote_edited)
assertEquals(expectedLabel, Message.editedLabel.waitForText(expectedLabel))
return this
}

Expand All @@ -143,7 +141,7 @@ fun UserRobot.assertDeletedMessage(text: String? = null, hard: Boolean = false):
fun UserRobot.assertQuotedMessage(text: String, quote: String = "", isDisplayed: Boolean = true): UserRobot {
val quotedMessageInList = Message.quotedMessage.hasAncestor(MessageListPage.MessageList.messages)
if (isDisplayed) {
assertEquals(quote, quotedMessageInList.waitToAppear().waitForText(quote).text)
assertEquals(quote, quotedMessageInList.waitForText(quote))
} else {
assertFalse(quotedMessageInList.waitToDisappear().isDisplayed())
}
Expand Down Expand Up @@ -231,14 +229,13 @@ fun UserRobot.assertMentionWasApplied(): UserRobot {
val additionalSpace = " "
val userName = ParticipantRobot.name
val expectedText = "@${userName}$additionalSpace"
val actualText = Composer.inputField.findObject().waitForText(expectedText).text
val actualText = Composer.inputField.waitForText(expectedText)
assertEquals(expectedText, actualText)
return this
}

fun UserRobot.assertComposerText(expectedText: String): UserRobot {
val actualText = Composer.inputField.waitToAppear().text
assertEquals(expectedText, actualText)
assertEquals(expectedText, Composer.inputField.waitForText(expectedText))
return this
}

Expand Down Expand Up @@ -268,27 +265,20 @@ fun UserRobot.assertThreadReplyLabelOnParentMessage(): UserRobot {
1,
1,
)
assertEquals(
expectedResult,
Message.threadRepliesLabel.waitToAppear().text,
)
assertEquals(expectedResult, Message.threadRepliesLabel.waitForText(expectedResult))
assertTrue(Message.threadParticipantAvatar.isDisplayed())
return this
}

fun UserRobot.assertAlsoInTheChannelLabelInChannel(): UserRobot {
assertEquals(
appContext.getString(R.string.stream_compose_replied_to_thread),
Message.messageHeaderLabel.waitToAppear().text,
)
val expectedLabel = appContext.getString(R.string.stream_compose_replied_to_thread)
assertEquals(expectedLabel, Message.messageHeaderLabel.waitForText(expectedLabel))
return this
}

fun UserRobot.assertAlsoInTheChannelLabelInThread(): UserRobot {
assertEquals(
appContext.getString(R.string.stream_compose_also_sent_to_channel),
Message.messageHeaderLabel.waitToAppear().text,
)
val expectedLabel = appContext.getString(R.string.stream_compose_also_sent_to_channel)
assertEquals(expectedLabel, Message.messageHeaderLabel.waitForText(expectedLabel))
return this
}

Expand Down Expand Up @@ -352,15 +342,15 @@ fun UserRobot.assertThreadReplyLabel(replies: Int, inThread: Boolean = false): U
)
assertEquals(
expectedResult,
ThreadPage.ThreadList.repliesCountLabel.waitToAppear().waitForText(expectedResult).text,
ThreadPage.ThreadList.repliesCountLabel.waitForText(expectedResult),
)
} else {
val expectedResult = appContext.resources.getQuantityString(
R.plurals.stream_compose_message_list_thread_footnote,
replies,
replies,
)
assertEquals(expectedResult, Message.threadRepliesLabel.waitToAppear().text)
assertEquals(expectedResult, Message.threadRepliesLabel.waitForText(expectedResult))
}
return this
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,6 @@ class QuotedReplyTests : StreamTestCase() {
}

@AllureId("5898")
@Ignore("https://linear.app/stream/issue/AND-1136")
@Test
fun test_quotedReplyInThreadAndAlsoInChannel() {
val quotedText = messagesCount.toString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,41 @@
package io.getstream.chat.android.compose.uiautomator

import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.StaleObjectException
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until

public fun sleep(timeOutMillis: Long = defaultTimeout) {
Thread.sleep(timeOutMillis)
}

/**
* Waits up to [timeOutMillis] for an object matching this selector and returns it.
*
* @param timeOutMillis Maximum time to wait before failing.
* @throws IllegalStateException when the timeout elapses without a matching object.
*/
public fun BySelector.waitToAppear(timeOutMillis: Long = defaultTimeout): UiObject2 {
wait(timeOutMillis)
return findObject()
return device.findObject(this)
?: error("waitToAppear timed out after ${timeOutMillis}ms; no object matched selector: $this")
}

/**
* Waits up to [timeOutMillis] for objects matching this selector and returns the one at [withIndex].
*
* @param withIndex The zero-based index of the object to return.
* @param timeOutMillis Maximum time to wait before failing.
* @throws IllegalStateException when the timeout elapses without enough matching objects.
*/
public fun BySelector.waitToAppear(withIndex: Int, timeOutMillis: Long = defaultTimeout): UiObject2 {
wait(timeOutMillis)
return findObjects()[withIndex]
val objects = device.findObjects(this)
return objects.getOrNull(withIndex)
?: error(
"waitToAppear(withIndex=$withIndex) timed out after ${timeOutMillis}ms; " +
"only ${objects.size} objects matched selector: $this",
)
}

public fun BySelector.wait(timeOutMillis: Long = defaultTimeout): BySelector {
Expand All @@ -44,17 +64,43 @@ public fun BySelector.waitToDisappear(timeOutMillis: Long = defaultTimeout): ByS
return this
}

public fun UiObject2.waitForText(
/**
* Waits for an object matching this selector whose text matches [expectedText]. Returns the
* matched text, or the last observed text on timeout. Never throws — recompositions and
* mid-poll node recycling are absorbed internally, so callers should wrap the result in an
* assertion to surface mismatch/timeout.
*
* @param expectedText The text to match.
* @param mustBeEqual When `true`, requires exact match; otherwise a substring match.
* @param timeOutMillis Maximum time to keep polling before returning the last observed text.
*/
public fun BySelector.waitForText(
expectedText: String,
mustBeEqual: Boolean = true,
timeOutMillis: Long = defaultTimeout,
): UiObject2 {
): String {
val endTime = System.currentTimeMillis() + timeOutMillis
var textPresent = false
while (!textPresent && System.currentTimeMillis() < endTime) {
textPresent = if (mustBeEqual) text == expectedText else text.contains(expectedText)
var lastText = ""
while (System.currentTimeMillis() < endTime) {
val actual = currentTextOrNull()
if (actual != null) {
lastText = actual
val matches = if (mustBeEqual) actual == expectedText else actual.contains(expectedText)
if (matches) return actual
}
Thread.sleep(POLL_INTERVAL_MILLIS)
}
return this
return lastText
}

private const val POLL_INTERVAL_MILLIS = 50L

// Call [device] directly — [findObject] lies about nullability and NPEs when the selector hasn't
// matched yet, which is the normal case during polling.
private fun BySelector.currentTextOrNull(): String? = try {
device.findObject(this)?.text
} catch (_: StaleObjectException) {
null
}

public fun BySelector.waitForCount(count: Int, timeOutMillis: Long = defaultTimeout): List<UiObject2> {
Expand Down
Loading