Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-01 11:12:02 +02:00
parent a65b0ab877
commit 846f6301d9
3 changed files with 37 additions and 3 deletions

View file

@ -41,6 +41,36 @@ so the hold gesture is silently swallowed: the button looks fine, the user holds
3. Snapshot again — byte-identical trees mean `onConfirm` didn't run. 3. Snapshot again — byte-identical trees mean `onConfirm` didn't run.
4. Or check WireMock request stats for the downstream API call expected after `onConfirm`. 4. Or check WireMock request stats for the downstream API call expected after `onConfirm`.
## `assertTextContains(x)` defaults to exact-segment match, not substring
`SemanticsNodeInteraction.assertTextContains(value, substring = false, ignoreCase = false)` defaults to
`substring = false` — it asserts that some text **segment of the node equals `value` exactly**. Matching
a symbol or fragment inside a larger string (e.g. `"€"` against a balance `"€108,474.21"`) silently
never matches and times out inside a `waitUntil`. Pass `substring = true`:
```kotlin
totalBalanceText.assertTextContains("€", substring = true)
```
Reference tests that pass the *full* string (`assertTextContains("€108,474.21")`) work with the default,
which is why a copy-pasted matcher can mislead.
## Kakao-Compose `child { }`: use DSL matchers, not raw Compose matcher aliases
Inside a `child { … }` / `ComposeScreen` element builder, call the DSL methods (`hasText(...)`,
`hasTestTag(...)`, `hasAnyDescendant(...)`). A common alias is `import androidx.compose.ui.test.hasText
as withText` — but `withText(x)` as a **bare statement** inside the builder just creates a
`SemanticsMatcher` and discards it, registering nothing → `ViewBuilderException: Please set matchers for
your Element!` at run time. `withText`/raw matchers are only valid as *arguments* to a DSL method
(`hasAnyDescendant(withText(name))`), never as a standalone line.
```kotlin
// WRONG — no matcher registered
fun walletNameValue(name: String) = child { withText(name); useUnmergedTree = true }
// RIGHT
fun walletNameValue(name: String) = child { hasText(name); useUnmergedTree = true }
```
## Decompose model lifecycle vs. data refresh ## Decompose model lifecycle vs. data refresh
Models (e.g. `TangemPayDetailsModel`) call data fetches from `init {}`, NOT on `ON_RESUME`. Returning Models (e.g. `TangemPayDetailsModel`) call data fetches from `init {}`, NOT on `ON_RESUME`. Returning

View file

@ -71,7 +71,7 @@ class WalletSettingsPageObject(semanticsProvider: SemanticsNodeInteractionsProvi
} }
fun walletNameValue(name: String): KNode = walletSettingsItem.child { fun walletNameValue(name: String): KNode = walletSettingsItem.child {
withText(name) hasText(name)
useUnmergedTree = true useUnmergedTree = true
} }

View file

@ -70,7 +70,9 @@ class AppCurrencyTest : BaseTestCase() {
step("Assert total balance contains '$targetSymbol' on 'Main' screen") { step("Assert total balance contains '$targetSymbol' on 'Main' screen") {
// Balance re-loads in the new currency async after the switch — wait for the € equivalent. // Balance re-loads in the new currency async after the switch — wait for the € equivalent.
composeTestRule.waitUntil(WAIT_UNTIL_TIMEOUT_LONG) { composeTestRule.waitUntil(WAIT_UNTIL_TIMEOUT_LONG) {
runCatching { onMainScreen { totalBalanceText.assertTextContains(targetSymbol) } }.isSuccess runCatching {
onMainScreen { totalBalanceText.assertTextContains(targetSymbol, substring = true) }
}.isSuccess
} }
} }
step("Click on token '$token'") { step("Click on token '$token'") {
@ -78,7 +80,9 @@ class AppCurrencyTest : BaseTestCase() {
} }
step("Assert token fiat balance contains '$targetSymbol'") { step("Assert token fiat balance contains '$targetSymbol'") {
composeTestRule.waitUntil(WAIT_UNTIL_TIMEOUT_LONG) { composeTestRule.waitUntil(WAIT_UNTIL_TIMEOUT_LONG) {
runCatching { onTokenDetailsScreen { fiatBalance.assertTextContains(targetSymbol) } }.isSuccess runCatching {
onTokenDetailsScreen { fiatBalance.assertTextContains(targetSymbol, substring = true) }
}.isSuccess
} }
} }
} }