From 9e5dc4fece0f57e5cfc7d24ab5fff4bec5b181e8 Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Thu, 22 Oct 2026 22:15:03 +0000 Subject: [PATCH] ui tests: Android compile fixes (WorkManager future, app version), reveal a loaded but unshown item once, quote test scrolls to the reply --- .../simplex/app/ui/android/AndroidHelpers.kt | 13 +++++++++---- .../java/chat/simplex/app/ui/harness/UiTest.kt | 6 ++++++ .../kotlin/chat/simplex/app/ui/harness/UiTest.kt | 2 ++ .../simplex/app/ui/chat/ReceivedMessagesTest.kt | 8 +++++++- .../chat/simplex/app/ui/harness/Screens.kt | 16 ++++++++++++++-- .../app/ui/settings/VersionDevToolsTest.kt | 5 +---- .../2026-10-10-desktop-ui-test-dsl-plan.md | 2 +- 7 files changed, 40 insertions(+), 12 deletions(-) diff --git a/apps/multiplatform/android/src/androidTest/java/chat/simplex/app/ui/android/AndroidHelpers.kt b/apps/multiplatform/android/src/androidTest/java/chat/simplex/app/ui/android/AndroidHelpers.kt index b0021230a5..fa87360ed3 100644 --- a/apps/multiplatform/android/src/androidTest/java/chat/simplex/app/ui/android/AndroidHelpers.kt +++ b/apps/multiplatform/android/src/androidTest/java/chat/simplex/app/ui/android/AndroidHelpers.kt @@ -45,11 +45,16 @@ internal fun serviceNotification(): Notification? = internal fun receiverState(receiver: Class<*>): Int = androidAppContext.packageManager.getComponentEnabledSetting(ComponentName(androidAppContext.packageName, receiver.name)) -internal fun uniqueWorkActive(name: String): Boolean = - androidAppContext.getWorkManagerInstance().getWorkInfosForUniqueWork(name).get().any { !it.state.isFinished } +internal fun uniqueWorkActive(name: String): Boolean = uniqueWorkInfos(name).any { !it.state.isFinished } -internal fun workStates(name: String): List = - androidAppContext.getWorkManagerInstance().getWorkInfosForUniqueWork(name).get().map { it.state } +internal fun workStates(name: String): List = uniqueWorkInfos(name).map { it.state } + +@Suppress("UNCHECKED_CAST") +private fun uniqueWorkInfos(name: String): List { + val wm = androidAppContext.getWorkManagerInstance() + val future = wm.javaClass.getMethod("getWorkInfosForUniqueWork", String::class.java).invoke(wm, name) as java.util.concurrent.Future<*> + return future.get() as List +} internal fun UiScope.withBackgroundServiceAllowed(body: () -> Unit) { val pkg = androidAppContext.packageName diff --git a/apps/multiplatform/android/src/androidTest/java/chat/simplex/app/ui/harness/UiTest.kt b/apps/multiplatform/android/src/androidTest/java/chat/simplex/app/ui/harness/UiTest.kt index ffa65ebdc3..a105e75622 100644 --- a/apps/multiplatform/android/src/androidTest/java/chat/simplex/app/ui/harness/UiTest.kt +++ b/apps/multiplatform/android/src/androidTest/java/chat/simplex/app/ui/harness/UiTest.kt @@ -133,6 +133,12 @@ class UiScope(val rule: ComposeTestRule) { poll(what = "the soft keyboard to hide") { !keyboardShown() } } + fun appVersion(): Pair { + val ctx = androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().targetContext + val info = ctx.packageManager.getPackageInfo(ctx.packageName, 0) + return info.versionName!! to info.longVersionCode.toInt() + } + fun assumeCamera() { val cameras = androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().targetContext .getSystemService(android.hardware.camera2.CameraManager::class.java).cameraIdList diff --git a/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/ui/harness/UiTest.kt b/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/ui/harness/UiTest.kt index 0f8c5132ae..e7344979e1 100644 --- a/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/ui/harness/UiTest.kt +++ b/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/ui/harness/UiTest.kt @@ -423,6 +423,8 @@ class UiScope(val ui: SkikoComposeUiTest, private val dir: File) { .flatMap { it.config.getOrElseNullable(androidx.compose.ui.semantics.SemanticsProperties.Text) { null }.orEmpty() } .map { it.text } + fun appVersion(): Pair = chat.simplex.common.BuildConfigCommon.DESKTOP_VERSION_NAME to chat.simplex.common.BuildConfigCommon.DESKTOP_VERSION_CODE + fun assumeCamera() {} fun hideKeyboard() {} diff --git a/apps/multiplatform/common/src/uiTestShared/kotlin/chat/simplex/app/ui/chat/ReceivedMessagesTest.kt b/apps/multiplatform/common/src/uiTestShared/kotlin/chat/simplex/app/ui/chat/ReceivedMessagesTest.kt index 497b76f83b..c848ba31a2 100644 --- a/apps/multiplatform/common/src/uiTestShared/kotlin/chat/simplex/app/ui/chat/ReceivedMessagesTest.kt +++ b/apps/multiplatform/common/src/uiTestShared/kotlin/chat/simplex/app/ui/chat/ReceivedMessagesTest.kt @@ -42,7 +42,13 @@ class ReceivedMessagesTest : UiTest() { bob types ">@alice (hello) all good - you?" bob expectsMessage "@alice > hello! how are you?" bob expects " all good - you?" - val reply = chat.itemId("all good - you?") + var reply = 0L + poll(timeoutMs = CONNECT_TIMEOUT_MS, what = "bob's reply `all good - you?` in alice's chat") { + reply = chatModel.chatsContext.chatItems.value.lastOrNull { it.content.text == "all good - you?" }?.id ?: 0L + reply != 0L + } + hideKeyboard() + poll(what = "bob's reply scrolled into view") { exists(Ui.Chat.Item(reply)) || run { scrollChatTowardNewer(); false } } poll(what = "bob's reply bubble quoting `you` / `hello! how are you?` above `all good - you?`") { textsOf(Ui.Chat.Item(reply)).let { t -> "you" in t && t.any { "hello! how are you?" in it } && t.any { "all good - you?" in it } } } diff --git a/apps/multiplatform/common/src/uiTestShared/kotlin/chat/simplex/app/ui/harness/Screens.kt b/apps/multiplatform/common/src/uiTestShared/kotlin/chat/simplex/app/ui/harness/Screens.kt index 46845cf7ca..9c700030e6 100644 --- a/apps/multiplatform/common/src/uiTestShared/kotlin/chat/simplex/app/ui/harness/Screens.kt +++ b/apps/multiplatform/common/src/uiTestShared/kotlin/chat/simplex/app/ui/harness/Screens.kt @@ -242,9 +242,15 @@ class ChatScreen(private val s: UiScope) { fun itemId(text: String): Long { var id: Long? = null + var revealed = false s.poll(what = "chat item `$text`") { id = chatModel.chatsContext.chatItems.value.lastOrNull { it.content.text == text }?.id - id != null && s.exists(Ui.Chat.Item(id!!)) + val shown = id != null && s.exists(Ui.Chat.Item(id!!)) + if (id != null && !shown && !revealed && s.exists(Ui.Chat.ScrollToBottom)) { + revealed = true + s.click(Ui.Chat.ScrollToBottom) + } + shown } return id!! } @@ -305,10 +311,16 @@ class ChatScreen(private val s: UiScope) { fun itemId(what: String, match: (ChatItem) -> Boolean): Long { var id: Long? = null + var revealed = false s.poll(timeoutMs = CONNECT_TIMEOUT_MS, what = what) { val found = chatModel.chatsContext.chatItems.value.lastOrNull(match)?.id id = found - found != null && s.exists(Ui.Chat.Item(found)) + val shown = found != null && s.exists(Ui.Chat.Item(found)) + if (found != null && !shown && !revealed && s.exists(Ui.Chat.ScrollToBottom)) { + revealed = true + s.click(Ui.Chat.ScrollToBottom) + } + shown } return id!! } diff --git a/apps/multiplatform/common/src/uiTestShared/kotlin/chat/simplex/app/ui/settings/VersionDevToolsTest.kt b/apps/multiplatform/common/src/uiTestShared/kotlin/chat/simplex/app/ui/settings/VersionDevToolsTest.kt index 9010edaef1..560a5fab1f 100644 --- a/apps/multiplatform/common/src/uiTestShared/kotlin/chat/simplex/app/ui/settings/VersionDevToolsTest.kt +++ b/apps/multiplatform/common/src/uiTestShared/kotlin/chat/simplex/app/ui/settings/VersionDevToolsTest.kt @@ -5,7 +5,6 @@ import chat.simplex.app.ui.harness.chatConsole import chat.simplex.app.ui.harness.closeDeveloperTools import chat.simplex.app.ui.harness.consoleShows import chat.simplex.app.ui.harness.developerTools -import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.appPlatform import chat.simplex.common.platform.chatController @@ -27,9 +26,7 @@ private const val TALK_TO_SOMEONE = "Talk to someone" class VersionDevToolsTest : UiTest() { @Test fun version_screenShowsVersions() = uiTest { val alice = app("alice") - val (name, code) = - if (appPlatform.isDesktop) BuildConfigCommon.DESKTOP_VERSION_NAME to BuildConfigCommon.DESKTOP_VERSION_CODE - else BuildConfigCommon.ANDROID_VERSION_NAME to BuildConfigCommon.ANDROID_VERSION_CODE + val (name, code) = appVersion() val core = runBlocking { chatController.apiGetVersion() } ?: error("the core did not return its version") alice.settings() assertEquals("$name ($code)", textOf(Ui.Settings.Version)) diff --git a/plans/ui-tests/2026-10-10-desktop-ui-test-dsl-plan.md b/plans/ui-tests/2026-10-10-desktop-ui-test-dsl-plan.md index 8905325edf..7011381023 100644 --- a/plans/ui-tests/2026-10-10-desktop-ui-test-dsl-plan.md +++ b/plans/ui-tests/2026-10-10-desktop-ui-test-dsl-plan.md @@ -1677,7 +1677,7 @@ Fixed in the same review: `createGroup_opensChatAndInviteSheet` now checks the o 24. **Desktop: the system theme detector thread crashes without a session D-Bus** (jSystemThemeDetector `GnomeThemeDetector$DetectorThread.run`, `GnomeThemeDetector.java:119`: `readLine` returns null when `gsettings monitor` exits, then `NullPointerException` on `split`). Seen on every GitHub runner job (2026-10-22): an Ubuntu runner has no session bus, so `gsettings monitor` exits at once. A user on a Linux desktop without a session bus or without the GNOME schemas gets the same uncaught exception in a background thread; theme following stops. The `ui-tests` workflow runs the desktop tests with a session bus on a filesystem socket, so the tests do not hit it. Related: memory of an NPE in the library's KDE path on an unset `XDG_CURRENT_DESKTOP`. -25. **A message that arrives while its chat is being opened is missing from the open chat** (desktop; formerly open flake 15.4.1b). The chat list preview shows the new message with an unread count, but the open chat's items never get it until the chat is reopened. Rare locally (1 of 4 runs), 5 tests in one GitHub runner run, where the runner is slower. `ChatListScreen.open()` now waits for app events to settle (`quiesce()`) before it clicks the row, so other tests do not race into it; `ReceivedMessagesTest.openChat_whileMessagesArrive_showsAll` clicks the row while bob's messages are arriving and is marked `@KnownAppIssue("15.5 item 25", intermittent = true)`. +25. **After opening a chat with a new message, the message is missing or out of view** (desktop; formerly open flake 15.4.1b). The chat list preview shows the new message with an unread count; the open chat shows its beginning, with the unread button showing 1, and the message is either not in the open chat's items at all (seen when it arrived while the chat was being opened) or loaded but laid out outside the visible area (seen in `timed_chatPref_receivedMessagesDisappear` on 2026-10-22, after `open()` had waited for events to settle). Rare locally (1 of 4 runs), 5 tests in one GitHub runner run, where the runner is slower. `ChatListScreen.open()` now waits for app events to settle (`quiesce()`) before it clicks the row, and `ChatScreen.itemId()` taps the unread button once, as a user would, when the item is loaded but not shown; `ReceivedMessagesTest.openChat_whileMessagesArrive_showsAll` clicks the row while bob's messages are arriving and is marked `@KnownAppIssue("15.5 item 25", intermittent = true)`. ### 15.6 Catalogue corrections applied from passing tests (exhaustive pass)