6.5 KiB
Yield Mode (yield-supply) UI tests
Hard-won specifics for testing the Yield Mode feature (features/yield-supply, analytics category
"Earning"). The first test (app/.../tests/yield/YieldModeTest.kt, case #4938 "first-time landing
activation") is the reference — mirror it. Read this before writing any yield test.
What the feature is
Depositing a stablecoin (USDC/USDT) into a DeFi protocol (Aave) from the app to earn APY. Distinct from
Staking (domain/staking) but both render the shared EarnBlock (common/ui/.../earn/) on token
details. Activation = a real signed transaction (approve + enter), so it needs a hot wallet
(mock card can't sign). Use openMainScreenWithExistingHotWallet(SVS_SEED_PHRASE_12).
Network: use Ethereum, NOT Polygon
- Ethereum USDC activation is a native ETH transaction → the standard send path works.
- Polygon USDC activation routes through the gasless flow (
GASLESS_APPROVAL_ENABLED=true+ Polygon USDC is gasless-eligible) →gaslessTransaction(); the SDK'sEthereumTransactionValidatorthrowsFailedToSendExceptionsynchronously and the send never completes. Avoid Polygon for yield send/activation tests unless you specifically mock the whole gasless-v2 stack. - Wallet (SVS_SEED_PHRASE_12) EVM address:
0x3369554b994908d249d307b105f8e5e3115615c2. - Ethereum USDC contract:
0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48(chainId 1, decimals 6).
Running (same as the general doc, repeated because it bit us)
Run via the orchestrator, never raw am instrument (with am instrument the fresh-wallet portfolio
silently never loads):
curl -s -X POST http://localhost:8081/__admin/mappings/reset # after editing mocks
./gradlew :app:connectedGoogleMockedAndroidTest \
-Pandroid.testInstrumentationRunnerArguments.class=com.tangem.tests.yield.YieldModeTest \
-Pandroid.testInstrumentationRunnerArguments.wiremockBaseUrl=http://10.0.2.2:8081
WireMock on 8081 is a docker container bind-mounting the tangem-api-mocks working tree — mappings/reset
reloads from disk, no rebuild. App-side logs (TangemLogger) also reach logcat; the failure semantics
tree is dumped to logcat (ComposeTree).
Mock set (in tangem-api-mocks, branch feature/AND-16082_yield_mode_mocks)
- Yield API (
mappings/yield-api/api/v1/):yield/markets,yield/token/1/{usdc}(+/chart),module/activate,module/deactivate. Base URL iswiremock.tests-d.com(see infra fix below) so the path is/api/v1/yield/.... - On-chain eth_call (
mappings/providers/ethereum/eth-call-yield.json, priority 1 to beat the genericeth-call.json): factorygetModule(to=0xd8972a45...), processor service-fee (to=0x4ff6178b...), yield status0xf8e8be9c+ balances0x16a398f7/0x5002bb7e(to = module0x1111…), allowance0xdd62ed3e(to = USDC) → 0. Status/balances gated by scenarioyield_supply_status(NotActive=zeros, Active=…01,…01,…8ac7230489e80000). - History (
mappings/eth-blockbook.nownodes.io/): NowNodes BlockBook v2. Ethereum mainnet tx history is NowNodes eth-blockbook, NOT Etherscan and NOT/v2/transaction-events(the latter is a push-dedup POST). The host is redirected by the interceptor (infra fix below). - Portfolio: scenario
user_tokens_apistateYieldUSDCEthereum(accounts API; native ETH + USDC), balances viamoralis_evm_token_balances_api=NonZeroEvmBalances.
Scenarios the test drives
user_tokens_api=YieldUSDCEthereum, moralis_evm_token_balances_api=NonZeroEvmBalances, and
yield_supply_status NotActive → (after activation) Active. The Active flip is what turns the
EarnBlock from "available" into the active "Yield Mode enabled / Average APY" state AND surfaces the
history row.
Gotchas that cost real time
isActiveinyield/markets&yield/tokenmeans "market is available", not "user activated". It MUST betrueorYieldSupplyTokenStatusSuccessTransformerreturnsUnavailableand the block never renders. User activation is tracked separately by the on-chaineth_callstatus flip.yieldSupplyKeymatching is case-sensitive string equality ("${backendId}_$tokenAddress"vs"${network.rawId}_$contractAddress"). All addresses in mocks must be lowercase — every existing mock is. A checksummed address yieldsyieldSupplyApy size=0and no available block.- Active vs available is driven by
CryptoCurrencyStatus.value.yieldSupplyStatus.isActive(on-chain eth_call0xf8e8be9c), not the APIisActive. Flip theyield_supply_statusscenario + pull-to-refresh. - Infra fix (production, already applied):
YieldSupplyApiConfig MOCK base URL wasyield.tests-d.com(NOT redirected byWireMockRedirectInterceptor) → fixed towiremock.tests-d.com. Andeth-blockbook.nownodes.iowas added toREDIRECTABLE_THIRD_PARTY_HOSTS. Without these the yield/history requests bypass WireMock entirely. - Hold-to-confirm must wait for the fee. The "Start earning"
HoldToConfirmButtonis disabled (holdToConfirmGestures(enabled=false)swallows the gesture) until the fee is calculated. Don't gate on the high-fee notification (it's absent on cheap chains) — use the fee-agnostic retry: a single step thatflakySafely-retrieslongClick(HOLD_DURATION_MS)then asserts the sheet closed (startEarningButton.assertIsNotDisplayed()).state.isConfirmedmakes re-holds no-ops, so no double-send. A hold step that finishes in ~270 ms instead of ~2 s = the gesture was swallowed (button disabled). - The yield-enter history row is a
TransactionItemUM.Pill(converter mapsYieldSupply.Enter→ Pill, like Approve/Staking), rendered byTransactionStatusPill. It now carriesTransactionHistoryItemTestTags.ITEM(added for parity withContentItem) sotransactionItem(title)finds it. Title string =yield_module_transaction_enter("Yield Mode enabled" / "Режим доходности подключен"). The row opens the explorer on click. - "Nothing to add to TxHistory" in logs is NOT an error — it refers to recent/pending txs, separate from the API history.
testTags added to production (reuse, don't re-add)
TokenDetailsScreenTestTags.YIELD_SUPPLY_BLOCK / YIELD_SUPPLY_AVAILABLE_BLOCK; YieldSupplyTestTags
(PROMO_CONTINUE_BUTTON, START_EARNING_BUTTON); TransactionHistoryItemTestTags.ITEM now on
TransactionStatusPill. Page objects: yield locators in TokenDetailsPageObject,
YieldSupplyPromoPageObject, YieldSupplyStartEarningPageObject.