mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-31 22:22:41 +00:00
ui tests: Android auto-advancing clock again, live-message tests skipped on Android; repeatable KnownAppIssue; finding 29
This commit is contained in:
1 parent
a2ed9e1680
commit
2146bd2ab4
7 files changed
+35
-10
No files matched your search
+1
-2
@@ -35,10 +35,9 @@ abstract class UiTest {
|
||||
val watchdog = Watchdog.start(HARD_TIMEOUT_MS)
|
||||
try {
|
||||
UiTestApp.bootOnce()
|
||||
rule.mainClock.autoAdvance = false
|
||||
AppCrashes.install()
|
||||
AppCrashes.clear()
|
||||
withKnownIssue(knownIssue.issue) {
|
||||
withKnownIssue(knownIssue.issues) {
|
||||
val scope = UiScope(rule)
|
||||
try {
|
||||
scope.body()
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ abstract class UiTest {
|
||||
try {
|
||||
AppCrashes.install()
|
||||
AppCrashes.clear()
|
||||
withKnownIssue(knownIssue.issue) {
|
||||
withKnownIssue(knownIssue.issues) {
|
||||
try {
|
||||
runInternalSkikoComposeUiTest(width = 1600, height = 900, coroutineDispatcher = StandardTestDispatcher()) {
|
||||
mainClock.autoAdvance = false
|
||||
|
||||
@@ -164,6 +164,8 @@ When you change the harness (`harness/*.kt`, both `UiTest.kt`), keep these prope
|
||||
- **A known app bug is a visible result, not a pass.** Mark the test `@KnownAppIssue("<plan §15.5 item>", "<exact
|
||||
error text>")`: when the test fails with that error it is reported as skipped with the finding; when it fails any
|
||||
other way it fails; when it passes, it fails with "known issue no longer reproduces; remove the annotation".
|
||||
`intermittent = true` lets the test pass when the bug does not show. A test can carry several annotations; a failure
|
||||
matching any of them is skipped, and a pass still fails while any of them is not intermittent.
|
||||
- **No retries of user input**, no repeated `replaceText` until the text sticks, no second Enter.
|
||||
- **App work still running when the test ends fails the test** (it is a hang or a leaked job), with the names of the
|
||||
stuck calls.
|
||||
|
||||
+7
@@ -8,13 +8,16 @@ import chat.simplex.app.ui.harness.UiTest
|
||||
import chat.simplex.app.ui.harness.backToChatList
|
||||
import chat.simplex.app.ui.harness.connect
|
||||
import chat.simplex.app.ui.harness.described
|
||||
import chat.simplex.common.platform.appPlatform
|
||||
import chat.simplex.common.platform.chatModel
|
||||
import chat.simplex.common.ui.Ui
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.Test
|
||||
|
||||
class LiveMessageTest : UiTest() {
|
||||
@Test fun liveMessage_menuStart_alertConfirm_peerSeesStarted() = uiTest {
|
||||
assumeTrue(LIVE_ON_ANDROID, !appPlatform.isAndroid)
|
||||
val alice = app("alice"); val bob = cli("bob")
|
||||
connect(bob)
|
||||
bob sends "/show on"
|
||||
@@ -45,6 +48,7 @@ class LiveMessageTest : UiTest() {
|
||||
|
||||
@KnownAppIssue("15.5 item 13", "[LIVE started] use /show", intermittent = true)
|
||||
@Test fun liveMessage_boltButton_emptyStart() = uiTest {
|
||||
assumeTrue(LIVE_ON_ANDROID, !appPlatform.isAndroid)
|
||||
val alice = app("alice"); val bob = cli("bob")
|
||||
connect(bob)
|
||||
alice.chatList.open("bob")
|
||||
@@ -95,6 +99,7 @@ class LiveMessageTest : UiTest() {
|
||||
}
|
||||
|
||||
@Test fun liveMessage_chatSwitch_sendsToOriginalChat() = uiTest {
|
||||
assumeTrue(LIVE_ON_ANDROID, !appPlatform.isAndroid)
|
||||
val alice = app("alice"); val bob = cli("bob")
|
||||
connect(bob)
|
||||
val chat = alice.chatList.open("bob")
|
||||
@@ -135,3 +140,5 @@ class LiveMessageTest : UiTest() {
|
||||
val CANCEL = described("Cancel live message")
|
||||
}
|
||||
}
|
||||
|
||||
private const val LIVE_ON_ANDROID = "Android: the harness cannot type while a live message is active (plan 15.4.1d)"
|
||||
+1
@@ -23,6 +23,7 @@ import org.junit.Test
|
||||
|
||||
class MemberReportTest : UiTest() {
|
||||
@KnownAppIssue("15.5 item 12", "bob: expected ` report content`, got ` report illegal`")
|
||||
@KnownAppIssue("15.5 item 29", "waiting for cath's `inappropriate joke`", intermittent = true)
|
||||
@Test fun report_member_spam_moderatorsReceive() = uiTest {
|
||||
val alice = app("alice"); val bob = cli("bob"); val cath = cli("cath")
|
||||
val chat = memberOfHostedGroup(alice, bob, cath)
|
||||
|
||||
+15
-7
@@ -7,29 +7,37 @@ import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Repeatable
|
||||
annotation class KnownAppIssue(val finding: String, val error: String, val intermittent: Boolean = false)
|
||||
|
||||
class KnownIssueRule : TestWatcher() {
|
||||
var issue: KnownAppIssue? = null
|
||||
var issues: List<KnownAppIssue> = emptyList()
|
||||
private set
|
||||
|
||||
override fun starting(description: Description) {
|
||||
issue = description.getAnnotation(KnownAppIssue::class.java)
|
||||
issues = description.annotations.flatMap { a ->
|
||||
when {
|
||||
a is KnownAppIssue -> listOf(a)
|
||||
a.annotationClass.java.enclosingClass == KnownAppIssue::class.java -> (a.annotationClass.java.getMethod("value").invoke(a) as Array<*>).filterIsInstance<KnownAppIssue>()
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun withKnownIssue(issue: KnownAppIssue?, block: () -> Unit) {
|
||||
if (issue == null) return block()
|
||||
fun withKnownIssue(issues: List<KnownAppIssue>, block: () -> Unit) {
|
||||
if (issues.isEmpty()) return block()
|
||||
try {
|
||||
block()
|
||||
} catch (e: AssumptionViolatedException) {
|
||||
throw e
|
||||
} catch (e: Throwable) {
|
||||
if (issue.error in e.stackTraceToString()) throw AssumptionViolatedException("known app issue (plan ${issue.finding}): ${issue.error}")
|
||||
val trace = e.stackTraceToString()
|
||||
issues.firstOrNull { it.error in trace }?.let { throw AssumptionViolatedException("known app issue (plan ${it.finding}): ${it.error}") }
|
||||
throw e
|
||||
}
|
||||
if (!issue.intermittent) throw AssertionError("known app issue (plan ${issue.finding}) no longer reproduces; remove @KnownAppIssue")
|
||||
println("UITEST known app issue (plan ${issue.finding}) did not reproduce in this run")
|
||||
issues.firstOrNull { !it.intermittent }?.let { throw AssertionError("known app issue (plan ${it.finding}) no longer reproduces; remove @KnownAppIssue") }
|
||||
issues.forEach { println("UITEST known app issue (plan ${it.finding}) did not reproduce in this run") }
|
||||
}
|
||||
|
||||
object AppCrashes {
|
||||
|
||||
@@ -1591,6 +1591,12 @@ Each entry gives the symptom, the cause (how it was confirmed), and the fix or m
|
||||
- On Android, while alice's chat is open, bob's 30 short messages and his reply arrive and the list stays where it was (top); the scroll-down arrow is shown. Tapping it through `ChatScreen.scrollToBottom()` (key `Ui.Chat.ScrollToBottom`) left the list at the top in the failure screenshot, and clicking the reply's quote then timed out with the node still moving. With two 60-line messages instead, Android positioned the chat inside the tall message. Marking the chat read and reopening it opened it at the first unread message.
|
||||
- Not resolved: whether the arrow tap is lost, or the app scrolls back (an animated `scrollToBottom` racing item loading), is not known. The test passes on desktop; on Android it is skipped with this reason (`assumeTrue`), so it shows as skipped, not passed. Next step: log `listState.firstVisibleItemIndex` around the arrow tap.
|
||||
|
||||
### 15.4.1d Android cannot drive a live message (`LiveMessageTest`)
|
||||
|
||||
- While a live message is active, the send button pulses in an endless animation (`SendMsgView.kt:543-552`). With Compose's auto-advancing test clock (Android's default), `waitForIdle` then never returns, and the three tests that start a live message failed with `ComposeNotIdleException` (GitHub emulator, 2026-10-22).
|
||||
- Pausing the clock, as desktop does, breaks typing on Android instead (CI trial 6, 2026-10-23: 77 failures waiting for the message field to hold the typed text). The message field is an `AndroidView` `EditText`. Its update block reads `composeState`, so a text change re-runs it before the paused clock lets the field recompose. It then compares against `cs` captured at the last composition (`PlatformTextField.android.kt:69`, `:186-188`) and writes the old text back.
|
||||
- These three tests (`liveMessage_menuStart_alertConfirm_peerSeesStarted`, `liveMessage_boltButton_emptyStart`, `liveMessage_chatSwitch_sendsToOriginalChat`) type while the message is live, so they are skipped on Android with this reason (`assumeTrue`) and run on desktop. `liveMessage_received_showsLiveBadge` runs on both.
|
||||
|
||||
### 15.4.2 Test strength limits (second review, 2026-10-14)
|
||||
|
||||
Under the minimal-app-code rule (item 31) some states are asserted through the model, not the UI. These tests pass if the model is right even when the UI fails to render the state:
|
||||
@@ -1685,6 +1691,8 @@ Fixed in the same review: `createGroup_opensChatAndInviteSheet` now checks the o
|
||||
|
||||
28. **The content filter menu does not update while a chat is open** (`ChatView.kt:206`, `:812`; `ChatSearchDesktopTest.contentFilter_fileAddedInOpenChat_listsFiles`, marked `@KnownAppIssue("15.5 item 28")`). The available content types are fetched only when the chat opens or when search closes through an item jump, so a file that arrives, or a file note added, does not make "Files" appear in the menu until the chat is reopened.
|
||||
|
||||
29. **A message received in an open group chat is missing from the chat, though the chat list shows it** (desktop, GitHub CI trial 6, 2026-10-23, 1 of 101 tests in the shard; `MemberReportTest.report_member_spam_moderatorsReceive`, marked `@KnownAppIssue("15.5 item 29", intermittent = true)`). alice's `team` chat was opened and settled (`chatModel.chatId` set, no API call in flight) before cath sent `inappropriate joke`. 60 s later the row showed `cath: inappropriate joke` with unread 1, but the open chat's `chatItems` did not have it; the chat showed only the merged `cath and bob connected and 1 other events`. Cause not found. Candidate, not confirmed: a pagination load after the open (`ChatView.kt:541-546` → `apiLoadMessages`) publishes a snapshot read before the message arrived, through the same two-step publication as item 25 (`ChatItemsLoader.kt:64-91`).
|
||||
|
||||
### 15.6 Catalogue corrections applied from passing tests (exhaustive pass)
|
||||
|
||||
- 01:34 and 01:8: accepting conditions enables the operators selected in onboarding, not all preset ones.
|
||||
|
||||
Reference in new issue
Block a user