From 35920403d63c209f27162bce12a11032335f484f Mon Sep 17 00:00:00 2001 From: Alexey Alter-Pesotskiy Date: Fri, 10 Jan 2025 14:22:11 +0000 Subject: [PATCH 1/4] [CI] Add ChannelList E2E tests --- .../chat/android/compose/robots/UserRobot.kt | 5 + .../robots/UserRobotChannelListAsserts.kt | 18 +- .../android/compose/tests/ChannelListTests.kt | 211 +++++++++++++++++- .../android/compose/tests/MessageListTests.kt | 14 +- .../android/e2e/test/uiautomator/Actions.kt | 7 + .../android/e2e/test/uiautomator/Element.kt | 2 +- 6 files changed, 235 insertions(+), 22 deletions(-) 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 09fe743c94d..75d1be7e03b 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 @@ -58,6 +58,11 @@ class UserRobot { return this } + fun waitForChannelListToLoad(): UserRobot { + ChannelListPage.ChannelList.channels.wait() + return this + } + fun openChannel(channelCellIndex: Int = 0): UserRobot { ChannelListPage.ChannelList.channels.wait().findObjects()[channelCellIndex].click() return this 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 12109e2e365..2e5892ec5af 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 @@ -17,12 +17,17 @@ package io.getstream.chat.android.compose.robots import io.getstream.chat.android.compose.pages.ChannelListPage.ChannelList.Channel +import io.getstream.chat.android.compose.pages.MessageListPage.MessageList.Message +import io.getstream.chat.android.compose.uiautomator.device +import io.getstream.chat.android.compose.uiautomator.findObjects import io.getstream.chat.android.compose.uiautomator.isDisplayed import io.getstream.chat.android.compose.uiautomator.wait import io.getstream.chat.android.compose.uiautomator.waitToAppear +import io.getstream.chat.android.compose.uiautomator.waitToDisappear import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue +import java.io.ByteArrayOutputStream fun UserRobot.assertChannelAvatar(): UserRobot { assertTrue(Channel.avatar.isDisplayed()) @@ -36,8 +41,8 @@ fun UserRobot.assertMessageInChannelPreview(text: String, fromCurrentUser: Boole return this } -fun UserRobot.assertMessageDeliveryStatus(shouldBeVisible: Boolean, shouldBeRead: Boolean = false): UserRobot { - if (shouldBeVisible) { +fun UserRobot.assertMessageDeliveryStatus(isDisplayed: Boolean, shouldBeRead: Boolean = false): UserRobot { + if (isDisplayed) { val readStatus = if (shouldBeRead) Channel.readStatusIsRead else Channel.readStatusIsSent assertTrue(readStatus.wait().isDisplayed()) } else { @@ -46,3 +51,12 @@ fun UserRobot.assertMessageDeliveryStatus(shouldBeVisible: Boolean, shouldBeRead } return this } + +fun UserRobot.assertMessagePreviewTimestamp(isDisplayed: Boolean = true): UserRobot { + if (isDisplayed) { + assertTrue(Channel.timestamp.waitToAppear().isDisplayed()) + } else { + assertFalse(Channel.timestamp.waitToDisappear().isDisplayed()) + } + return this +} \ No newline at end of file diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelListTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelListTests.kt index baad3bd0a3b..c01acf0b0d4 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelListTests.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelListTests.kt @@ -19,8 +19,16 @@ package io.getstream.chat.android.compose.tests import io.getstream.chat.android.compose.robots.assertChannelAvatar import io.getstream.chat.android.compose.robots.assertMessageDeliveryStatus import io.getstream.chat.android.compose.robots.assertMessageInChannelPreview +import io.getstream.chat.android.compose.robots.assertMessagePreviewTimestamp +import io.getstream.chat.android.compose.uiautomator.device +import io.getstream.chat.android.compose.uiautomator.disableInternetConnection +import io.getstream.chat.android.compose.uiautomator.enableInternetConnection +import io.getstream.chat.android.e2e.test.mockserver.AttachmentType +import io.getstream.chat.android.e2e.test.mockserver.forbiddenWord import io.qameta.allure.kotlin.Allure.step import io.qameta.allure.kotlin.AllureId +import okhttp3.internal.wait +import org.junit.Ignore import org.junit.Test class ChannelListTests : StreamTestCase() { @@ -31,9 +39,7 @@ class ChannelListTests : StreamTestCase() { @Test fun test_channelPreviewUpdates_whenParticipantSendsMessage() { step("GIVEN user opens a channel") { - userRobot - .login() - .openChannel() + userRobot.login().openChannel() } step("WHEN participant sends a message") { participantRobot.sendMessage(sampleText) @@ -44,7 +50,7 @@ class ChannelListTests : StreamTestCase() { step("THEN user observes the new message in preview") { userRobot .assertMessageInChannelPreview(sampleText, false) - .assertMessageDeliveryStatus(shouldBeVisible = false) + .assertMessageDeliveryStatus(isDisplayed = false) .assertChannelAvatar() } } @@ -53,9 +59,7 @@ class ChannelListTests : StreamTestCase() { @Test fun test_channelPreviewUpdates_whenUserSendsMessage() { step("GIVEN user opens a channel") { - userRobot - .login() - .openChannel() + userRobot.login().openChannel() } step("WHEN user sends a message") { userRobot.sendMessage("Test") @@ -66,14 +70,203 @@ class ChannelListTests : StreamTestCase() { step("THEN user observes the new message in preview") { userRobot .assertMessageInChannelPreview(sampleText, true) - .assertMessageDeliveryStatus(shouldBeVisible = true, shouldBeRead = false) + .assertMessageDeliveryStatus(isDisplayed = true, shouldBeRead = false) .assertChannelAvatar() } step("WHEN participant reads the message") { participantRobot.readMessage() } step("THEN user observes the new message in preview") { - userRobot.assertMessageDeliveryStatus(shouldBeVisible = true, shouldBeRead = true) + userRobot.assertMessageDeliveryStatus(isDisplayed = true, shouldBeRead = true) + } + } + + @AllureId("6679") + @Test + fun test_channelPreviewUpdates_whenUserIsOfflineAndParticipantSendsMessage() { + step("GIVEN user opens a channel list") { + userRobot.login().waitForChannelListToLoad() + } + step("AND user goes offline") { + device.disableInternetConnection() + } + step("WHEN participant sends a message") { + participantRobot.sendMessage(sampleText) + } + step("AND user goes back online") { + device.enableInternetConnection() + } + step("THEN user observes the new message in preview") { + userRobot.assertMessageInChannelPreview(sampleText, false) + } + } + + @AllureId("5785") + @Test + fun test_errorMessageIsNotShownInChannelPreview() { + step("GIVEN user opens the channel") { + userRobot.login().openChannel() + } + step("AND participant sends a message") { + participantRobot.sendMessage(sampleText) + } + step("WHEN user sends a message with invalid command") { + userRobot.sendMessage("/test") + } + step("AND user goes back to the channel list") { + userRobot.tapOnBackButton() + } + step("THEN the error message is not shown in preview") { + userRobot + .assertMessageInChannelPreview(sampleText, false) + .assertMessagePreviewTimestamp() + } + } + + @AllureId("5796") + @Ignore("https://linear.app/stream/issue/AND-218") + @Test + fun test_channelPreviewShowsNoMessages_whenChannelIsEmpty() { + step("WHEN user opens channel list") { + userRobot.login() + } + step("AND the channel has no messages") { + // No actions required as the channel is empty by default + } + step("THEN the channel preview shows No messages") { + userRobot.assertMessageInChannelPreview("No messages", fromCurrentUser = false) + } + step("AND the message timestamp is hidden") { + userRobot.assertMessagePreviewTimestamp(isDisplayed = false) + } + } + + @AllureId("5798") + @Ignore("https://linear.app/stream/issue/AND-218") + @Test + fun test_channelPreviewShowsNoMessages_whenTheOnlyMessageInChannelIsDeleted() { + step("GIVEN user opens the channel") { + userRobot.login().openChannel() + } + step("AND participant sends a message") { + participantRobot.sendMessage(sampleText) + } + step("AND participant deletes the message") { + participantRobot.deleteMessage() + } + step("WHEN user goes back to the channel list") { + userRobot.tapOnBackButton() + } + step("THEN the channel preview shows No messages") { + userRobot.assertMessageInChannelPreview("No messages", fromCurrentUser = false) + } + step("AND the message timestamp is hidden") { + userRobot.assertMessagePreviewTimestamp(isDisplayed = false) + } + } + + @AllureId("5821") + @Test + fun test_channelPreviewShowsPreviousMessage_whenLastMessageIsDeleted() { + val oldMessage = "Old" + val newMessage = "New" + + step("GIVEN user opens the channel") { + userRobot.login().openChannel() + } + step("AND participant sends 2 messages") { + participantRobot + .sendMessage(oldMessage) + .sendMessage(newMessage) + } + step("AND participant deletes the last message") { + participantRobot.deleteMessage() + } + step("WHEN user goes back to the channel list") { + userRobot.tapOnBackButton() + } + step("THEN the channel preview shows previous message") { + userRobot.assertMessageInChannelPreview(oldMessage, fromCurrentUser = false) + } + step("AND the message timestamp is shown") { + userRobot.assertMessagePreviewTimestamp(isDisplayed = true) + } + } + + @AllureId("5799") + @Test + fun test_channelPreviewIsNotUpdated_whenThreadReplyIsSent() { + val channelMessage = "Channel message" + val threadReply = "Thread reply" + + step("GIVEN user opens the channel") { + userRobot.login().openChannel() + } + step("AND participant sends a message") { + participantRobot.sendMessage(channelMessage) + } + step("AND participant adds thread reply to this message") { + participantRobot.sendMessageInThread(threadReply) + } + step("WHEN user goes back to the channel list") { + userRobot.tapOnBackButton() + } + step("THEN the channel preview shows the channel message preview") { + userRobot.assertMessageInChannelPreview(channelMessage, fromCurrentUser = false) + } + step("AND the message timestamp is shown") { + userRobot.assertMessagePreviewTimestamp(isDisplayed = true) + } + } + + @AllureId("6680") + @Test + fun test_channelPreviewIsUpdated_whenThreadReplyIsSentAlsoInTheChannel() { + val channelMessage = "Channel message" + val threadReply = "Thread reply" + + step("GIVEN user opens the channel") { + userRobot.login().openChannel() + } + step("AND user sends a message") { + userRobot.sendMessage(channelMessage) + } + step("AND user adds thread reply to this message also in the channel") { + userRobot.openThread().sendMessageInThread(threadReply, alsoSendInChannel = true) + } + step("WHEN user goes back to the channel list") { + userRobot.moveToChannelListFromThreadList() + } + step("THEN the channel preview shows the thread message preview") { + userRobot.assertMessageInChannelPreview(threadReply, fromCurrentUser = true) + } + step("AND the message timestamp is shown") { + userRobot.assertMessagePreviewTimestamp(isDisplayed = true) + } + } + + @AllureId("5820") + @Test + fun test_channelPreviewIsUpdated_whenPreviewMessageIsEdited() { + val editedMessage = "edited message" + + step("GIVEN user opens the channel") { + userRobot.login().openChannel() + } + step("AND participant sends a message") { + participantRobot.sendMessage(sampleText) + } + step("WHEN participant edits the message") { + participantRobot.editMessage(editedMessage) + } + step("AND user goes back to the channel list") { + userRobot.tapOnBackButton() + } + step("THEN the channel preview shows edited message") { + userRobot.assertMessageInChannelPreview(editedMessage, fromCurrentUser = false) + } + step("AND the message timestamp is shown") { + userRobot.assertMessagePreviewTimestamp(isDisplayed = true) } } } diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/MessageListTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/MessageListTests.kt index f94e28a0db1..d5027b71365 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/MessageListTests.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/MessageListTests.kt @@ -57,9 +57,7 @@ class MessageListTests : StreamTestCase() { @Test fun test_messageListUpdates_whenParticipantSendsMessage() { step("GIVEN user opens a channel") { - userRobot - .login() - .openChannel() + userRobot.login().openChannel() } step("WHEN participant sends a message") { participantRobot.sendMessage(sampleText) @@ -75,9 +73,7 @@ class MessageListTests : StreamTestCase() { @Test fun test_messageListUpdates_whenUserSendsMessage() { step("GIVEN user opens a channel") { - userRobot - .login() - .openChannel() + userRobot.login().openChannel() } step("WHEN user sends a message") { userRobot.sendMessage(sampleText) @@ -257,10 +253,8 @@ class MessageListTests : StreamTestCase() { @Test fun test_typingIndicator() { step("GIVEN user opens the channel") { - userRobot - .login() - .openChannel() - .sendMessage(sampleText) + backendRobot.generateChannels(channelsCount = 1, messagesCount = 1) + userRobot.login().openChannel() } step("WHEN participant starts typing") { participantRobot.startTyping() diff --git a/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/uiautomator/Actions.kt b/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/uiautomator/Actions.kt index 4aed111f13b..dbf9e7a9939 100644 --- a/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/uiautomator/Actions.kt +++ b/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/uiautomator/Actions.kt @@ -20,6 +20,7 @@ import android.content.Intent import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiObject2 import io.getstream.chat.android.e2e.test.mockserver.mockServerUrl +import java.io.ByteArrayOutputStream public fun UiDevice.startApp() { val intent = testContext.packageManager.getLaunchIntentForPackage(packageName) @@ -95,3 +96,9 @@ public fun UiDevice.disableInternetConnection() { executeShellCommand("svc data disable") executeShellCommand("svc wifi disable") } + +public fun UiDevice.dumpWindowHierarchy() { + val outputStream = ByteArrayOutputStream() + device.dumpWindowHierarchy(outputStream) + println(outputStream.toString("UTF-8")) +} diff --git a/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/uiautomator/Element.kt b/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/uiautomator/Element.kt index 5303ebfa581..f9493f29ae8 100644 --- a/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/uiautomator/Element.kt +++ b/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/uiautomator/Element.kt @@ -20,7 +20,7 @@ import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.UiObject2 public fun UiObject2.isDisplayed(): Boolean { - return this.isFocusable + return this.visibleCenter.y > 0 } public fun BySelector.isDisplayed(): Boolean { From 5e240f72261bf5123422367818d6d554a8dcc1d7 Mon Sep 17 00:00:00 2001 From: Alexey Alter-Pesotskiy Date: Mon, 13 Jan 2025 16:03:33 +0000 Subject: [PATCH 2/4] Resolve lint issues --- .../android/compose/robots/UserRobotChannelListAsserts.kt | 6 +----- .../chat/android/compose/tests/ChannelListTests.kt | 3 --- .../api/stream-chat-android-e2e-test.api | 1 + 3 files changed, 2 insertions(+), 8 deletions(-) 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 2e5892ec5af..dcb52dd69d9 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 @@ -17,9 +17,6 @@ package io.getstream.chat.android.compose.robots import io.getstream.chat.android.compose.pages.ChannelListPage.ChannelList.Channel -import io.getstream.chat.android.compose.pages.MessageListPage.MessageList.Message -import io.getstream.chat.android.compose.uiautomator.device -import io.getstream.chat.android.compose.uiautomator.findObjects import io.getstream.chat.android.compose.uiautomator.isDisplayed import io.getstream.chat.android.compose.uiautomator.wait import io.getstream.chat.android.compose.uiautomator.waitToAppear @@ -27,7 +24,6 @@ import io.getstream.chat.android.compose.uiautomator.waitToDisappear import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue -import java.io.ByteArrayOutputStream fun UserRobot.assertChannelAvatar(): UserRobot { assertTrue(Channel.avatar.isDisplayed()) @@ -59,4 +55,4 @@ fun UserRobot.assertMessagePreviewTimestamp(isDisplayed: Boolean = true): UserRo assertFalse(Channel.timestamp.waitToDisappear().isDisplayed()) } return this -} \ No newline at end of file +} diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelListTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelListTests.kt index c01acf0b0d4..b8b25a4858b 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelListTests.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/ChannelListTests.kt @@ -23,11 +23,8 @@ import io.getstream.chat.android.compose.robots.assertMessagePreviewTimestamp import io.getstream.chat.android.compose.uiautomator.device import io.getstream.chat.android.compose.uiautomator.disableInternetConnection import io.getstream.chat.android.compose.uiautomator.enableInternetConnection -import io.getstream.chat.android.e2e.test.mockserver.AttachmentType -import io.getstream.chat.android.e2e.test.mockserver.forbiddenWord import io.qameta.allure.kotlin.Allure.step import io.qameta.allure.kotlin.AllureId -import okhttp3.internal.wait import org.junit.Ignore import org.junit.Test diff --git a/stream-chat-android-e2e-test/api/stream-chat-android-e2e-test.api b/stream-chat-android-e2e-test/api/stream-chat-android-e2e-test.api index aed782a0bfd..d5d2a98813d 100644 --- a/stream-chat-android-e2e-test/api/stream-chat-android-e2e-test.api +++ b/stream-chat-android-e2e-test/api/stream-chat-android-e2e-test.api @@ -1,5 +1,6 @@ public final class io/getstream/chat/android/compose/uiautomator/ActionsKt { public static final fun disableInternetConnection (Landroidx/test/uiautomator/UiDevice;)V + public static final fun dumpWindowHierarchy (Landroidx/test/uiautomator/UiDevice;)V public static final fun enableInternetConnection (Landroidx/test/uiautomator/UiDevice;)V public static final fun goToBackground (Landroidx/test/uiautomator/UiDevice;)V public static final fun goToForeground (Landroidx/test/uiautomator/UiDevice;)V From 01049ca3a374be718392524fe40eff74b4a38de1 Mon Sep 17 00:00:00 2001 From: Alexey Alter-Pesotskiy Date: Wed, 15 Jan 2025 11:44:50 +0000 Subject: [PATCH 3/4] Resolve syntax issue --- .../io/getstream/chat/android/compose/tests/GiphyTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/GiphyTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/GiphyTests.kt index 43c45bc01a1..d029196197b 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/GiphyTests.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/GiphyTests.kt @@ -109,7 +109,7 @@ class GiphyTests : StreamTestCase() { } step("AND the previous message has timestamp and delivery status shown") { userRobot - .assertMessageDeliveryStatus(shouldBeVisible = true) + .assertMessageDeliveryStatus(isDisplayed = true) .assertMessageTimestamps(count = 1) } } From 4b4fc6252cec5c3245fb9f4d0574eb977389d2b1 Mon Sep 17 00:00:00 2001 From: Alexey Alter-Pesotskiy Date: Wed, 15 Jan 2025 12:07:17 +0000 Subject: [PATCH 4/4] Fix flaky test --- .../chat/android/compose/robots/UserRobotMessageListAsserts.kt | 1 + 1 file changed, 1 insertion(+) 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 df709369fd6..a466de60aa0 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 @@ -131,6 +131,7 @@ fun UserRobot.assertComposerSize(isChangeable: Boolean): UserRobot { } else { val text = "1\n2\n3\n4\n5\n6" typeText(text) + sleep(500) initialComposerHeight = composer.findObject().height typeText("${text}\n7") assertEquals(initialComposerHeight, composer.findObject().height)