ui tests: Android compile fixes (WorkManager future, app version), reveal a loaded but unshown item once, quote test scrolls to the reply

This commit is contained in:
Narasimha-sc committed 2026-10-22 22:15:03 +00:00
1 parent c58dd396e2
commit 9e5dc4fece
7 files changed
+40 -12

No files matched your search

@@ -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<WorkInfo.State> =
androidAppContext.getWorkManagerInstance().getWorkInfosForUniqueWork(name).get().map { it.state }
internal fun workStates(name: String): List<WorkInfo.State> = uniqueWorkInfos(name).map { it.state }
@Suppress("UNCHECKED_CAST")
private fun uniqueWorkInfos(name: String): List<WorkInfo> {
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<WorkInfo>
}
internal fun UiScope.withBackgroundServiceAllowed(body: () -> Unit) {
val pkg = androidAppContext.packageName
@@ -133,6 +133,12 @@ class UiScope(val rule: ComposeTestRule) {
poll(what = "the soft keyboard to hide") { !keyboardShown() }
}
fun appVersion(): Pair<String, Int> {
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
@@ -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<String, Int> = chat.simplex.common.BuildConfigCommon.DESKTOP_VERSION_NAME to chat.simplex.common.BuildConfigCommon.DESKTOP_VERSION_CODE
fun assumeCamera() {}
fun hideKeyboard() {}
@@ -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 } }
}
@@ -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!!
}
@@ -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))
@@ -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)