AI-generated analysisPublished automatically and not human-verified. Validated context appears in community notes below.
← Watch feed
Low 26 Monero

Integration tests (#3477)

Public commit record

What the developer wrote

Authored by David Adegoke

93/100 · Strong
Integration tests (#3477)

* fix android CI

* feat: Add integration test harness and new-ui test keys

This change:
- Adds a BaseRobot with bounded waits to replace hard sleeps in integration tests
- Adds AppLauncher, TestConfig and TestWallets as the core test harness
- Adds onboarding flows composing the existing onboarding robots
- Adds robots for the new dashboard, home page and lightning username page
- Adds a tier0 onboarding create suite targeting the new UI
- Applies the declared navbar action keys that were never attached to the InkWell
- Fixes AlertWithOneAction dropping its buttonKey since the alert redesign
- Adds stable ValueKeys to the home page action row, wallet name, top bar and sync bar
- Makes the legacy tap and text entry helpers wait for widgets instead of sleeping

* fix: Test secrets drift and integration test runner parameterization

This change:
- Adds missing test wallet seed and receive address entries to the secrets generator
- Fixes trailing space typos in two secret key names
- Adds bloc_test to pubspec_base.yaml so pubspec regeneration keeps it
- Parameterizes the integration test runner with suite dir, tier, platform and device knobs
- Fixes the runner aborting on first failure and misaligning durations after retries
- Adds adb data reset so Android suites start from a fresh install

* feat: Android emulator integration test workflows for dev PRs

This change:
- Adds a reusable integration test workflow building the app with prebuilt native deps and running the suites on an Android emulator
- Adds the PR gate workflow triggering fund-free tier0 suites on pull requests into dev
- Fixes the old workflow discarding the test runner exit code so failures now fail the job
- Removes the disabled automated integration test workflow on the stale flutter image
- Removes the commit-message gated Linux desktop test steps, unit tests stay
- Removes the vestigial emulator config from the reusable build workflow

* feat: Revive fund-free integration test suites for the new UI

This change:
- Adds tier0 suites for restore, seed confirmation, wallet switching, receive address, settings navigation and the show keys auth gate
- Adds tier1 suites for sync status, swap quotes and transaction history running non-blocking on PRs
- Adds robots for the settings, receive, swap and send sheets
- Adds auth, wallet switching and restore flows
- Applies widget keys to settings rows through their route names, they were declared but never attached
- Adds keys to the swap amount fields, send inputs, confirm swiper and history tiles
- Adds an integrationTest wrapper containing background async app errors that are not test failures
- Makes taps wait out running route transitions before hitting the screen
- Adds a watchdog to the test runner so a wedged driver fails bounded instead of hanging
- Removes the old UI suites, robots, helpers and the stale widget_test counter template

* chore: Add integration testing docs and suite template

This change:
- Adds docs/INTEGRATION_TESTS.md covering the architecture, local runs, tier rules and the flakiness playbook
- Adds a copy-paste suite template teammates start new feature tests from

* feat: Manual funds integration test workflow with funded-chain auto-discovery

This change:
- Adds a funds suite sending a real self transaction on every funded chain
- Adds a funds suite creating a real swap and broadcasting its deposit
- Discovers funded chains from the funded wallet seeds map, adding a seed to the secret adds the chain
- Adds the manual workflow dispatched from the Actions tab with flow, chain and recording inputs
- Gates the run behind the funds-tests environment and posts the result to slack
- Keeps funded seeds out of PR builds, the checked in seed map is empty

* fix: Harden integration test harness from review findings

This change:
- Fixes body failures being swallowed by the guarded test zone, failing suites hung until the watchdog instead of failing fast
- Fixes multi chain CHAINS dispatch values being split apart by the define forwarding
- Fixes the swap limits wait timing out on pairs without a provider minimum
- Makes unknown chain names in CHAINS fail with a readable message
- Keys favorite token rows by index, duplicate token symbols crashed on duplicate keys
- Guards the recording encryption so a keyserver flake cannot eat the test result
- Removes the dead security and backup robot and corrects the screenshot claim in the docs

* chore: Hold two funded seeds per chain in the funded wallets map

This change:
- Changes the funded wallet seeds map to a list of seeds per chain
- Keeps the first seed as the primary for sends and swaps, the second is reserved for cross wallet scenarios

* feat: Try each funded wallet per chain until one has a balance

This change:
- Adds a funds flow restoring a chain's funded wallets in turn and using the first with a spendable balance
- Treats a wallet that finishes syncing while still empty as drained so the next seed gets its turn quickly
- Fails a chain with a top up message when every funded wallet of that chain is empty

* reformat integration test related files

* fix: Run the integration test emulator on the kvm capable runner

This change:
- Moves the test job to the linux-amd64 runner the old emulator workflow targeted, the android builders have no /dev/kvm
- Exposes /dev/kvm through a docker device flag instead of a volume, a bind mount materializes a directory when the host node is missing
- Fails the kvm check in seconds with a clear error instead of waiting out the emulator boot timeout

* fix: Queue the integration test job on the live android runner pool

This change:
- Moves the job back to the android runner labels, no runner carries the old linux-amd64 label anymore so the job queued forever
- Keeps the kvm device flag and check so a host without kvm fails immediately with a clear error

* feat: Run the integration test gate on github hosted runners

This change:
- Moves the test job to ubuntu-latest which has working kvm, the self-hosted android builders do not expose /dev/kvm yet
- Runs directly on the vm since the cake container image does not fit hosted disk, flutter is pinned through the flutter action
- Adds rust android targets for the breez cargokit build and java 17 for gradle
- Keeps the prebuilt native deps from the ghcr docker cache, they pull fine on hosted docker
- Adds pub, gradle and flutter caching plus a disk cleanup step
- Keeps every input, the runner script, tier1 soft run, recording and artifacts unchanged

* fix: Install flutter from the same source as the ci container image

This change:
- Clones the flutter 3.41.9 tag directly like the container image instead of using the release archive
- The archive ships a stricter dart that rejects the checked in mweb ffi bindings, the tag clone compiles them
- Caches the cloned sdk between runs

* fix: Regenerate the mweb ffi bindings with the workflow dart

This change:
- Regenerates cw_mweb/lib/generated_bindings.g.dart after the deps step, the docker deps builder generates it with an older dart whose ffigen output the current compiler rejects

* fix: Pin ffigen so fresh resolutions generate compliant mweb bindings

This change:
- Pins ffigen to ^20.1.1 in cw_mweb, the unconstrained dependency resolved to 7.1.0 on fresh checkouts
- Old ffigen emits ffi classes without the base or final modifiers the current dart compiler requires

* fix: Restore the pinned cw_mweb pubspec before regenerating bindings

This change:
- Restores cw_mweb/pubspec.yaml after the deps step, the deps image rsync clobbers it with the stale unpinned copy so the ffigen pin never applied
- Wipes the rsynced pubspec.lock and .dart_tool so pub resolves the pinned ffigen fresh
- Asserts the regenerated bindings carry class modifiers so a bad generation fails in seconds
- Skips the libclang install when it is already present, apt spent fourteen minutes on it

* fix: Run the emulator test logic from a file, the action splits script lines

This change:
- Moves the whole emulator script into a file written before the emulator step, the action executes every script line as a separate sh command so multi line logic broke with a syntax error
- Passes the action a single line script invoking that file with bash

* fix: Give the ci emulator a real phone profile

This change:
- Sets the avd to the pixel 5 profile, the default 320x640 skin cannot fit the seed word grid so every wallet creation suite failed its seed display checks
- Caps tier0 attempts at ten minutes, two failing suites at the thirty minute worst case blew the job timeout

* fix: Wait for pages on the ci emulator instead of asserting immediately

This change:
- Makes the legacy page assertion poll for the page, the wallet keys page arrives after the ci emulator finishes unlocking the wallet so both auth gated suites failed on a race
- Raises the tier0 attempt timeout, the first attempt for each suite compiles that target's kernel and was being killed then passing on retry
- Raises the job timeout to fit the per suite compiles

* fix: Recover from driver attach failures instead of burning the timeout

This change:
- Detects attempts where the flutter driver never attached to the app and retries them without spending the real retry, restarting adb first
- Lowers the ci attempt timeout back to ten minutes, every passing attempt finishes within four so a longer cap only makes an attach failure cost more

* fix: Bound the adb recovery and give up when the driver never attaches

This change:
- Bounds every adb call in the recovery path, adb wait-for-device blocks forever when the emulator is gone and silently ate a whole ci job
- Aborts the suite when adb cannot be brought back instead of retrying against a dead device
- Stops the run after a few driver attach failures, an environment that never attaches will not fix itself
- Dumps logcat after every failed attempt so a failure to start the app leaves evidence behind

* fix: Stop the runner failing when every suite passes

This change:
- Assigns the result arrays empty instead of only declaring them, set -u treats a declared but never assigned array as unbound so the summary died on the first run where nothing failed

* fix: Make the ci build cacheable and keep tier results when the runner dies

This change:
- Splits the flutter and pub caches into restore and save steps, actions/cache only saves when the whole job succeeds so they were never written and every run paid for a cold build
- Lets setup-gradle write its cache, it is read only on branches other than the default one
- Caps the gradle heap before the test phase, the six gigabyte daemon plus the emulator left the runner too little to stay alive
- Runs tier0 and tier1 as separate emulator steps so each uploads its logs before the next can take the runner down
- Logs memory and disk around each tier
- Documents how ci runs the suites and the two environment quirks that only bite there

* fix: token decimals defaulting to zero and breaking amount parsing in solana

(cherry picked from commit c220bbb46df9468faf9a8967cb0ef672af7cc664)

* fix: Install the test error handler after the app boots and drain benign errors

This change:
- Installs the test error handler after main() instead of before it, the app replaces FlutterError.onError while booting so the handler installed earlier never ran
- Replaces the app's async error handler during tests, it returns at its first await without recording anything so the framework asserted instead of reporting the real error
- Drains background errors the app tolerates at runtime from the wait loops, anything unrecognised still fails the test where it happened
- Collects the tolerated categories in one place, layout and semantics assertions from debug builds plus the transient network failures exception_handler.dart already ignores

* fix: Import FlutterExceptionHandler from foundation so the test build compiles

This change:
- Imports package:flutter/foundation.dart in the launcher, material.dart only re-exports Brightness and UniqueKey from foundation so FlutterExceptionHandler was not in scope
- Drops the material.dart import, every symbol the launcher uses comes from foundation

* chore: Drop the funds-tests environment from the funds workflow and docs

This change:
- Removes the environment input from the funds workflow, we no longer use a funds-tests GitHub environment so passing it would only auto create an unprotected one on the next dispatch
- Says plainly in the docs that nothing gates the dispatch, the doc still claimed the run waits for environment approval
- Names write_funds_secrets as the one thing keeping funded seeds out of a PR build, that used to be backed by environment scoping too

* chore: Trim the integration test comments down to the ones that carry weight

This change:
- Drops the doc comment sitting on top of every robot primitive, flow and config getter, they restated the method name and nothing else
- Collapses the multi paragraph docstrings on the launcher, the error drain and the funded wallet lookup into the one or two lines that actually explain why the code is shaped that way
- Removes the file and class header paragraphs, the funded wallets stub keeps a short note because an empty map needs one
- Keeps the notes worth having, the chain quirks in the onboarding flow, the route transition tap, the dust thresholds, the void attempt handling in the runner

* chore: Fold the components folder into core and clear out the leftover comments

This change:
- Moves common_test_cases.dart into core, the components folder was down to that one file after common_test_constants.dart went
- Drops six methods from it that nothing calls, dragUntilVisible alone was 95 lines of scroll logic with no caller
- Removes the comments narrating the line below them across the older robots, "Drag to the left" above a moveBy and "Confirmation for buttons 1-9" above the loop
- Deletes the commented out body of takeScreenshots and says instead that it does nothing, all fifteen callers have been no-ops
- Notes in the docs that new robots extend BaseRobot while the older ones still carry a CommonTestCases

* chore: Clear out pending items

* refactor: Move the onboarding robots onto BaseRobot and drop the sleeps

This change:
- Deletes common_test_cases.dart, the sixteen robots that held a CommonTestCases now extend BaseRobot like the newer ones do
- Moves the primitives those robots needed onto BaseRobot with the same behaviour, isSpecificPage, hasType, hasValueKey, hasText, hasTextAtLeastOnce and swipePage
- Renames each robot's page check to isDisplayed so every robot answers the same question the same way
- Removes the twenty six defaultSleepTime calls, every one of them sat in front of an action that already waits for what it needs
- Waits for the view model to move to the next word in seed verification instead of sleeping a second and hoping, and fails with a message when it does not
- Settles after opening a tab on the wallet keys page, the assertions there read the tree straight after the tap
- Drops the fifteen takeScreenshots calls, the method's body has been commented out the whole time so none of them did anything
- Formats the three files dart format was still rewriting

* fix: Wait out the navigation the removed sleeps used to cover

This change:
- Settles after a page mounts in isSpecificPage, the route being replaced stays in the tree while it animates out and assertions on text were matching both screens, seed_confirmation_test failed on finding two "Verify Seed" widgets
- Settles after the last pin digit, the gate sends itself away and returning early landed the caller's next navigation on a locked navigator, wallet_switching_test failed on the !_debugLocked assertion

Both are places where waiting for a widget to exist is not the same as waiting for the app to
be idle, which is what the sleeps were quietly doing.

* fix: Recognise every way a flutter drive attach fails and give tier1 a retry

This change:
- Matches the other wordings flutter drive uses when the driver never attaches, we had only the connect one and missed the isolate initialise, the service disappeared and the driver extension variants
- Gives tier1 the same single retry tier0 has, running it with none meant a driver that never attached was reported as a failing test

Two of the three tier1 failures on the last run were attaches that never happened, neither
of them reached any test code.

* fix: Sync status test watched the wrong status class, history scroll had no guard

This change:
- Accepts SyncronizingSyncStatus in the sync status suite, cw_core spells it without the h and it is a separate class from SyncingSyncStatus, so chains reporting it sat out the full three minute timeout and then failed saying the wallet never synced while printing Synchronizing
- Waits for the home scroll view before handing it to scrollUntilVisible, which calls single on the finder every drag and reported a missing scroll view as a bare Bad state: No element
- Skips the scroll entirely when the history tiles are already on screen

* fix: Test the transaction history the way the screen actually works

The suite looked for history tiles on the home page and scrolled for more when it found
none, which could never pass. The home page renders the history under a tab, and for a
wallet with tokens the assets tab is the one selected first, so the history section was
never built. It also shows a three item preview, itemsShort caps it at
shortHistoryLength, and everything else sits behind the All button in a modal.

This change:
- Opens the history tab before looking for tiles, wallets with no assets tab render the history with no tab bar at all and skip that step
- Asserts the preview never renders more than the three the view model hands it, instead of scrolling for tiles that were never coming
- Opens the All button and checks the full list in the modal, which nothing covered before
- Adds keys for the tab items, the All button, the single tab history bar and the modal root

* fix: Stop the runner wiping a real wallet install and document what CI does that the docs did not

Following app_config.sh and then running the tests locally clears com.cakewallet.cake_wallet
between every suite, which is a developer's own wallets. CI never hits this because the
workflow renames the app afterwards, a step that only existed in the workflow.

This change:
- Refuses to clear a package that is not named for testing, with the rename command in the message
- Adds that rename to the local prerequisites, next to the reason it matters
- Spells out the assets/images vector pass, compile_graphics.sh does not cover that folder and those vec files are neither committed nor ignored, so a fresh checkout fails on the welcome screen
- Notes that a second Gradle daemon from Android Studio deadlocks the build

* test: Make the tier0 and tier1 assertions check what they claim to

An audit of every robot against the screen it drives turned up assertions that pass without
telling you anything, the same shape as the transaction history suite looking for tiles on a
tab it never opened.

This change:
- Reads the receive address off the screen instead of the view model, the view model holding the right address says nothing about what the user is told to send to, and reads both the chunked and the plain render because only handling the first comes back empty
- Verifies a restored wallet knows the address its seed derives, restoring the wrong wallet from a correct seed used to look the same as success, and this found two stale addresses in the secrets
- Replaces the sync bar check, the key it asserted sits on a widget that renders for every status including none, so it was true whenever the home page was up
- Requires the All transaction view to render more than the home preview, it could not tell the two apart before
- Fails the keys page check when a wallet type matches none of its branches instead of passing having verified nothing

* refactor: Remove outdated items from integration tests

* test: Wait for the wallet to actually change instead of pumping a fixed half second

Tapping a wallet row does nothing when that wallet is already open, and the load runs behind
a progress overlay, so a tap that went nowhere looked the same as a successful switch.

This change:
- Waits for the app store to report the wallet before returning, and fails with a message when it never does

* test: Open wallet groups before looking for a wallet by name

Wallets that share a seed are listed as children of their group's tile and are not on screen
while it is closed, so switching to one would time out looking for a name that is only
rendered once the group is opened. Single seed wallets carry their own name as the tile
title, which is why this has not come up yet.

This change:
- Opens the group tiles when the wallet is not already on screen

* chore: Keep the receive address widget diff to the key it needed

A formatter pass rewrote the whole file, quotes, body style and trailing commas, for what was
a one line addition. Restored the original shape so the diff shows only the key.

* chore: Drop trailing whitespace left by the comment cleanup

* test: Cover the send screen refusing what it cannot send

Nothing covered the send screen, which is the one place in the app where getting it wrong
costs the user money. The property worth holding is that the swiper, the point of no return,
is only ever offered for a transaction the wallet actually built.

This change:
- Adds a tier0 suite driving an empty form, an address the chain cannot parse, and a well formed address on a wallet that has never held anything, and requires that none of the three ever produce a swiper
- Waits for the swiper and requires it not to arrive rather than checking once, the screen passes through syncing, building and failed states on the way and any of them can be the one you catch
- Notes in the docs that a merge from dev leaves the generated chain proxies and the assets/images vec files behind, which has broken the local build three times now

* test: Add send and swap dry runs and a flows mode that never spends

The funds suites were all or nothing: the only way to find out whether a funded wallet still
works was to spend from it. Almost everything worth checking happens before the swipe, the
wallet finds its balance, prices the fee and builds a transaction, or the provider returns a
live quote, and none of that costs anything.

This change:
- Adds a send dry run that stops at the swiper, reaching it means the transaction was really built
- Adds a swap dry run that stops before the swap button, creating a trade registers an order with the provider even though no funds move, so it goes no further than the quote
- Adds a dry-run value to FLOWS that runs both and neither of the spending suites, and makes it the default so a mis-click cannot send anything

* test: Cover a wallet name that is already taken being refused

Two wallets answering to the same name is how someone ends up sending from the wrong one, and
nothing checked that the form stops it.

This change:
- Adds a tier0 suite that creates a wallet, starts a second one and gives it the same name, and requires the form to refuse it and stay put
- Splits the walk to the naming form out of createAdditionalWalletFromWalletList so a suite can stop there instead of going through with the wallet

* test: Cover a seed the wallet cannot parse restoring nothing

Restoring from a seed the wallet does not understand either fails outright or, worse, derives
some other wallet the user has no keys for. The restore button is disabled until the words
check out and nothing checked that.

This change:
- Adds a tier0 suite that types twelve words outside the wordlist, presses restore, and requires the form to stay put
- Follows it with the real seed in the same run, so the refusal above cannot pass just because the form restores nothing for any seed at all
- Splits the walk to the restore form out of restoreFirstWalletFromSeed so a suite can stop there

* refactor: Make spending its own switch instead of a value hidden in flows

flows says which flow to run, so folding a dry-run value into it made the dropdown answer two
questions at once and buried the one that matters.

This change:
- Adds a spend input, off by default, and gates the broadcasting suites on it
- Puts flows back to all, send or swap
- Runs the dry runs whenever their flow is picked, they cost nothing either way

* chore: cleanup minor issues

* test: Cover one seed backing wallets on two chains

Wallet groups had no coverage at all, and the part that would break quietly is the seed: a
group member that derived its own seed instead of sharing one looks identical from the wallet
list. It is also the only way a wallet ends up inside a collapsed group tile, which is the
path switchToWallet handles and nothing exercised.

This change:
- Adds a tier0 suite that creates a solana wallet, adds an ethereum wallet to its seed, and requires both to report the same seed
- Switches back to the first afterwards, which only works by opening the group it now sits in
- Adds the flow for it, a child wallet skips the seed and verification screens for a page explaining it shares one, so it cannot reuse the normal creation steps
- Adds the one key the group page was missing, its next button

* test: Cover changing the pin replacing the one that unlocks the wallet

Nothing checked that changing the pin actually takes. Watching the setup screen close proves
nothing, the only evidence is the new pin opening something the old one guarded.

This change:
- Adds a tier0 suite that changes the pin and then unlocks the seed and keys page with the new one
- Goes through the pin gate on the way in, so it also covers the current pin being required before the change is allowed

* test: Cover changing the language, and make settings rows and pickers addressable

Writing this turned up two things in the app rather than the test.

Only ListItemRegularRow was given its keyValue as a widget key. Toggles, checkboxes,
dropdowns and selectors all took the same keyValue and dropped it, so every settings control
that is not a plain navigation row could not be found at all.

Picker named three item types and gave everything else an empty string, so a picker over
plain values, the language list among them, rendered every row with the same key.

This change:
- Passes keyValue through as a key for the other four row types
- Falls back to the item itself for the picker name, so each row is distinct
- Adds a tier0 suite that changes the language and requires the row to show the new one

* test: Cover switching the node the wallet talks to

Which node a wallet is pointed at decides what it sees, and a switch that quietly does not
take leaves someone on a node they believe they left. Nothing covered it.

This change:
- Adds a tier1 suite that reads the current node, switches to another one, confirms, and requires the wallet to be pointed somewhere new
- tier1 rather than tier0 because opening the page speed tests every node against the network

* test: Cover the address book keeping the address it was given

Pasting an address once and trusting it afterwards is the whole point of an address book, so
an entry that comes back changed is a way to lose money quietly. Nothing covered it.

This change:
- Adds a tier0 suite that saves a contact through the form and requires the stored address to match what was typed
- Checks the saved record rather than the rows, the list filters what it shows by the currency in play so a contact can be saved and correctly not on screen
- Adds keys for the add button, the name field, the currency picker, the address field and save

* fix: Key picker rows on a name that does not change with the language

Enumerable items fell through to toString, which is translated for some of them, so the
same row carried a different key depending on the language in use.

* test: Cover the fiat currency setting, transaction details and sending to a contact

This change:
- Adds a suite for turning the fiat api off and on, which is what decides whether the
currency setting is offered at all, then changes the currency through it
- Adds a suite that taps a transaction and checks the details that open belong to it
- Adds a suite that saves a contact and picks it from the send screen
- Adds a suite for restoring a 25 word monero seed, which takes a seed type and a restore
height the polyseed path never asks for
- Keys the address book button on the send screen so a test can reach it
- Lets SUITE_DIR take a single suite file, for iterating on one test
- Lists every suite and what it proves in the integration tests doc

* test: Cover renaming and deleting a wallet

This change:
- Adds a suite for renaming a wallet, including the refusal when the name is already held
by another wallet
- Adds a suite for deleting a wallet, checking it leaves storage and not only the list
- Keys the edit button in the wallet list and the name field, the two buttons and both
dialogs on the wallet edit page, none of which could be reached from a test before
- Fixes the wallets list robot, which looked for a single Wallets title even though the
navbar carries the same word, and looked for wallet names across the whole tree even
though every dashboard tab stays mounted in an IndexedStack

* ci: Report every integration test run to slack

This change:
- Posts a report to slack on every run, naming each tier's counts, duration and any suite
that failed, with the full list of what passed in a reply on the same message
- Adds a SUMMARY_FILE knob to the runner, which writes what it counted in a form the
report can be built from rather than parsing the run log
- Skips with a notice when SLACK_APP_TOKEN or SLACK_TESTS_CHANNEL is missing, and never
fails the gate when slack is unreachable
- Leaves the funds workflow alone, the new slack_notify input defaults to off

* chore: Removing unneeded items

* ci: Say which slack scope was missing when a report is rejected

A rejection carries the scope it wanted in a field of its own, and dropping it left the
warning saying missing_scope without saying what to go and add.

* ci: Report funds runs to the tests channel and say when slack refuses them

This change:
- Sends the funds report to SLACK_TESTS_CHANNEL, so both test workflows land in one place
and the apk channel is left to builds
- Checks the slack response, the post was going out unchecked so a rejected report looked
like a delivered one
- Builds the message from the environment instead of interpolating workflow inputs into
the shell, a quote in a dispatch input was enough to break the payload

* ci: Give the test reports their own slack app

Uploading apks and reporting test results are different jobs with different scopes, and
the name on a message is how anyone in the channel tells which one sent it. Both test
workflows now post through SLACK_TESTS_TOKEN, which only needs chat:write, and the apk
uploads keep SLACK_APP_TOKEN and its file scopes.

* fix: Restore the swap amount key and say when a suite never ran

The AnyPay redesign moved the deposit amount field into SwapAmountBox and it lost the key
the swap suite enters an amount through, so that suite could no longer reach it. Both
amount fields are keyed now, deposit and receive.

This change also:
- Records whether a failed suite failed on its own or because the driver never attached,
which is what happened to send_validation_test in run 31364715120 where the suite never
actually ran
- Rebuilds the slack report out of blocks, so the status is a heading, the tiers sit in
their own fields, failures name their reason and the run is a button rather than a bare
url

* perf: End an attempt early when the driver cannot attach

flutter drive says it is struggling to attach within about a minute and then, when it is
really wedged, sits there until TEST_TIMEOUT kills it. Run 31364715120 lost 20 minutes to
two attempts doing exactly that.

This change:
- Watches the attempt log and ends the attempt VOID_GRACE seconds after the driver first
reports trouble, 120 by default, so the retry starts on a fresh app instead of waiting
out the remaining eight minutes
- Starts that clock at the first complaint rather than at the attempt, since the attempt
spends its first minute building and installing
- Moves the marker list into one variable, the watchdog and the after the fact check were
going to drift apart otherwise

* perf: Drop the driver grace to 60s and take the whole drive down with it

Measured against run 31364715120: the 19 attempts that attached did so in under a second,
while the two that never attached had the warning out at 5 seconds. 60 leaves a wide
margin over a healthy attach on a runner that was already running slow, and saves another
minute on each dead attempt.

Ending the attempt now walks the process tree rather than signalling the wrapper. On linux
the drive runs under timeout, and a KILL to that cannot be forwarded, so the drive would
have been left holding the device just as the retry went looking for it.

* trigger another test run

* Triggering another run, its been green so far

* fix: Wait for the wallet to be ready before tapping send in the funds suites

The send button is disabled until isReadyForSend, which wants a synced wallet, but the
funds flow moves on as soon as a balance appears. Every funded chain was therefore tapping
a disabled button and then waiting ninety seconds for a confirm sheet that could never
open. The robot now waits on the same value the screen gates on, so a wallet that never
syncs says so instead of looking like a missing widget.

* test: Make a funds send failure say what went wrong and stop it spreading

A send that never reaches the confirm sheet was reported as a missing ConfirmSwiper, which
says nothing about the cause. It now reports the state of whichever sheet is up, what the
wallet holds, and the build error when the chain rejected the transaction.

Recovery between chains was a single pop, so the first chain to fail left the app somewhere
the next chain's restore could not start from and every chain after it failed too. It now
unwinds until the home page is back and stops the suite if it cannot get there.

* test: full testing of all the funded tests and fix issues that came up with them

* fix: update sync key to fix sync test

* fix: out of space error

* fix: integration test review issues

* fix: merge conflicts and update test suites

* fix: false test passes and add funded wallet passphrases
chore: remove unused integration test robot methods
feat: save integration test screenshots on Android
feat: pick the swap pair for the funds dispatch

* fix: Keep the runner output on the step log through a file descriptor

* fix: timeout because of runner crash

* test: handle new seed ui flow and fix hang for runner

* fix: emulator teardown not rendering succesfully
feat: use currency picker for selecting swap options for funded run selected swap currency

* test: add key for compact dot key for sync and link to robot

* minor fixes

* test: review fixes for integration tests
✓ Descriptive subject✓ Provides detailed explanatory context✓ Explains rationale or failure mode✓ Mentions testing or verification✓ Links an issue, advisory, or supporting reference✓ Names security-relevant behavior explicitly
The short version

What changed, and why it matters

This is a large commit that adds and reorganizes automated integration tests for the Cake Wallet app. Most of the changes are test code, CI workflow files, and small app-side widget key additions so tests can find on-screen elements. There is one minor production bug fix buried in the history: a Solana token-decimals default was corrected so amounts parse correctly. The commit does not appear to introduce a security vulnerability; if anything it improves quality assurance by adding many regression tests and hardening CI behavior.

Recommended action

Treat as a normal QA/CI improvement commit. Review the Solana token-decimals fix in isolation to confirm it is correct and complete. Verify that the empty funded_wallets.dart stub is never populated in the repository and that FUNDS_SECRETS_FILE remains a protected repository secret. No security patch or incident response is indicated.

Security signals we found

01

Large test-only refactor with no obvious malicious code

02

Production-side changes are additive widget keys and one Solana decimals fix

03

CI now posts Slack reports and supports manual funds-spending tests with a default-off SPEND flag

04

No new secret handling beyond existing repository secrets and a stub funded_wallets.dart kept empty in git

05

No changes to cryptography, key storage, transaction signing, or network trust boundaries

Risk score

Why this scored 26/100

Our methodology →
Potential impact 5/30
Exploitability 2/25
Stealth signal 3/15
Affected reach 4/15
Confidence 8/10
Evidence quality 4/5
Human-validated context

Community notes

Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.

No validated notes yet.

The AI analysis stands alone for now. Submit a note if you can add evidence or important context.