diff --git a/Bitkit/ViewModels/AppViewModel.swift b/Bitkit/ViewModels/AppViewModel.swift index 08625a8f0..4e3c99cbf 100644 --- a/Bitkit/ViewModels/AppViewModel.swift +++ b/Bitkit/ViewModels/AppViewModel.swift @@ -904,7 +904,8 @@ extension AppViewModel { toast( type: .warning, title: t("other__lnurl_pay_error"), - description: t("other__lnurl_pay_error_no_capacity") + description: t("other__lnurl_pay_error_no_capacity"), + accessibilityIdentifier: "LnurlPayNoCapacityToast" ) return } diff --git a/journeys/hardware-wallet/README.md b/journeys/hardware-wallet/README.md index 8f1363d58..deb89f1bc 100644 --- a/journeys/hardware-wallet/README.md +++ b/journeys/hardware-wallet/README.md @@ -30,15 +30,20 @@ For the passphrase journeys, start it with passphrase protection instead: TREZOR_PASSPHRASE_PROTECTION=true ./scripts/trezor-emulator start ``` -Then build and run with the Bridge environment (mirrors the `xcodebuild` invocation in -`Docs/AI_DEVICE_TESTS.md`): +Then build and run with the Bridge settings, each passed as its own `--extra-args`: ```bash -TEST_TREZOR_EMU=1 TREZOR_BRIDGE=true TREZOR_BRIDGE_URL=http://127.0.0.1:21325 \ xcodebuildmcp simulator build-and-run \ - --extra-args "SWIFT_ACTIVE_COMPILATION_CONDITIONS=\$(inherited) E2E_BUILD TEST_TREZOR_EMU" + --extra-args "SWIFT_ACTIVE_COMPILATION_CONDITIONS=\$(inherited) E2E_BUILD" \ + --extra-args TREZOR_BRIDGE=true \ + --extra-args TREZOR_BRIDGE_URL=http://127.0.0.1:21325 ``` +Use the address the simulator reaches the emulator at in `TREZOR_BRIDGE_URL` when it is not +`127.0.0.1`. Do not add the `TEST_TREZOR_EMU` compilation condition that `Docs/AI_DEVICE_TESTS.md` +passes for the UI test suite: it replaces the wallet with the Trezor test dashboard, so no +journey that starts on the wallet home screen can run. + ## Order Several journeys mutate pairing state. Run them in this order, or re-pair between runs: @@ -46,7 +51,8 @@ Several journeys mutate pairing state. Run them in this order, or re-pair betwee 1. `connect-home-tile.xml` — pairs the emulator; every other journey assumes it ran. 2. `settings-hardware-wallets.xml`, `connect-flow.xml`, `suggestion-intro-sheet.xml` — each forgets and re-pairs the device, ending paired. -3. `activity-blue-icons.xml`, `activity-detail-hw-tags.xml`, `transfer-to-spending*.xml`, `reconnect.xml`. +3. `activity-blue-icons.xml`, `activity-detail-hw-tags.xml`, `transfer-to-spending*.xml`, `reconnect.xml`, + `send-onchain.xml`. 4. `passphrase-pairing.xml` → `passphrase-duplicate.xml` → `passphrase-transfer-to-spending.xml` → `passphrase-settings-remove.xml`. 5. `detail-overview.xml` **last** — its final step forgets the device. @@ -59,6 +65,15 @@ The device blocks until each prompt is acknowledged, once per address type for a ../bitkit-docker/scripts/trezor-emulator send-json '{"type":"emulator-press-yes","id":1}' ``` +The isolated `trezor-emulator` profile of `bitkit-docker` takes the same command through its own +container (`docker compose --profile trezor-emulator exec -T trezor-emulator +/trezor-user-env/.venv/bin/python3 /opt/bitkit-trezor/trezor-controller.py send-json +'{"type":"emulator-press-yes"}'`), which is how a stack that cannot use the host helper presses Yes. + +`send-onchain.xml` spends from the emulator's account, so it needs funds beyond what the transfer +journeys leave; fund the account's receive address again from the regtest node before it, and send +to an address of that node. + ## Checking for passphrase leaks Unlike the notification extension, the app writes its logs as **files in the app group container**, diff --git a/journeys/hardware-wallet/send-onchain.xml b/journeys/hardware-wallet/send-onchain.xml new file mode 100644 index 000000000..6500f6bc6 --- /dev/null +++ b/journeys/hardware-wallet/send-onchain.xml @@ -0,0 +1,31 @@ + + + Drives the on-chain Send sheet from a paired Trezor: Recipient -> Amount -> Review -> Sign With + Device -> Sent, then verifies the payment is listed once as an outgoing blue hardware activity + and that the hardware balance dropped by at least the amount. Requires a paired Bridge + emulator whose native-segwit account holds at least 50 000 confirmed regtest sats (fund the + emulator's receive address with the regtest bitcoin node and mine a block), and a valid regtest + destination address that does not belong to the emulator, for example a fresh address of the + regtest node. Start on the wallet home screen. + + + Note the sats amount shown on the hardware wallet tile (id "ActivityHardware") beneath the SAVINGS and SPENDING tiles + Tap the hardware wallet tile and verify the hardware wallet detail screen opens (id "HardwareWalletScreen") + Tap the Send button (id "Send") of the tab bar + If a camera permission dialog appears, dismiss it by choosing "Don't Allow" + Verify the Send sheet shows the recipient options, then tap "Enter Manually" (id "RecipientManual") + Type the destination regtest address into the recipient field (id "RecipientInput") and tap Continue (id "AddressContinue") + Verify the Send amount screen (id "SendAmount") is visible and its available amount (id "AvailableAmount") is greater than 0 + Tap the keys "1", "0", "0", "0", "0" (ids "N1", "N0", "N0", "N0", "N0") to enter 10 000 sats and tap Continue (id "ContinueAmount") + Verify the review screen (id "SendConfirm") shows an amount of 10 000 sats and the paired hardware wallet as the funding source (blue accent) + Tap "Show Details" (id "SendConfirmToggleDetails") and verify the destination address and a non-zero network fee are listed + Swipe the send control (id "GRAB") + Verify the sign screen (id "HardwareSendSign") is titled "SIGN WITH DEVICE", shows the destination address under "To address (confirm on device)" and the amount (id prefix "HardwareSendSignAmount") + Tap "Open Trezor Connect" (id "HardwareSendOpenTrezorConnect") + Approve the four prompts on the Bridge emulator in order (recipient, amount, fee and summary) with the controller command that presses Yes, as the README describes + Wait up to 60 seconds for signing and broadcast, then verify the Sent screen (id "SendSuccess") appears and shows 10 000 sats + Tap "Close" (id "Close"), mine one regtest block, wait for the app to sync, and verify the app is back on the hardware wallet detail screen (id "HardwareWalletScreen") + Verify the activity list of the hardware wallet detail screen contains exactly one new blue sent row above the older rows, whose amount is 10 000 sats plus the network fee shown in the review details + Navigate back to the wallet home screen and verify the hardware wallet tile shows a lower amount than the one noted at the start, by at least 10 000 sats + + diff --git a/journeys/lnurl/README.md b/journeys/lnurl/README.md new file mode 100644 index 000000000..7b992642d --- /dev/null +++ b/journeys/lnurl/README.md @@ -0,0 +1,36 @@ +# LNURL journeys + +## Setup + +LNURL-pay needs a reachable LNURL server. Start the `lnurl-pay` fixture of a sibling `bitkit-docker` +checkout (a service with stable pay metadata whose invoice callback keeps returning an error until +it is switched healthy): + +```bash +cd /path/to/bitkit-docker +docker compose --profile lnurl-pay up -d --build --wait lnurl-server-fixture +``` + +It serves on port 3010. Read the link to paste from it: + +```bash +curl -fsS http://127.0.0.1:3010/generate/pay # {"url":..., "lnurl":"lnurl1...", ...} +curl -fsS -X POST -H 'content-type: application/json' \ + -d '{"mode":"healthy"}' http://127.0.0.1:3010/fixture # or "error"; it starts in "error" +``` + +The callback URLs it returns follow the address the request used, so a simulator that reaches the +server at another address (a per-seat loopback address, `E2E_LOCAL_HOST`) gets callbacks at that +address. Build the app with the E2E condition and local backend as in [`../README.md`](../README.md) +so it may call plain HTTP on the local network. + +The fixture issues signed regtest invoices for fetching and decoding only. It has no Lightning +node and cannot settle a payment. + +## What is not covered + +Everything after the capacity check needs a Lightning spending balance: a channel to the LSP, which +the journey environment does not provide on an isolated stack. The failing and recovering invoice +callback (the send failure screen with a retry that succeeds once the callback is healthy) therefore +has no journey here yet. `payment-requests/definite-pre-broadcast-retry.xml` covers it for a +Payment Request on a wallet that has that balance. diff --git a/journeys/lnurl/pay-no-spending-balance.xml b/journeys/lnurl/pay-no-spending-balance.xml new file mode 100644 index 000000000..3702ebfe9 --- /dev/null +++ b/journeys/lnurl/pay-no-spending-balance.xml @@ -0,0 +1,26 @@ + + + Verifies that pasting an LNURL-pay link while the wallet has no Lightning spending balance is + refused with the capacity warning, and that the Send sheet stays on the recipient options instead + of opening the LNURL amount or confirmation screens. The app has to resolve the pay request from + the LNURL server first, because the minimum it compares the balance against comes from that + response, so this journey also proves the simulator reaches the server. + + Precondition: an onboarded E2E build on regtest with the node running and no channels, so the + Spending balance is zero. The LNURL-pay link is the `lnurl` field of the `GET /generate/pay` + response of the `bitkit-docker` `lnurl-server` fixture, read from the server address the app + reaches (see README.md). Copy it to the simulator clipboard (`xcrun simctl pbcopy <udid>`). + The fixture mode does not matter here, the invoice callback is never called. Start on the + wallet home screen. + + + Verify the SPENDING balance on the home screen is 0 + Tap the Send button (id "Send") + If a camera permission dialog appears, dismiss it by choosing "Don't Allow" + Tap "Paste Invoice" (id "RecipientInvoice") + If the iOS dialog about pasting from another app appears (the first paste of a build), choose "Allow Paste" + Straight after that, without waiting for another screen refresh because the toast fades after about 2.5 seconds, verify a warning toast titled "Unable To Pay (LNURL)" with the text "Not enough outbound/sending capacity to complete lnurl-pay request." appears (id "LnurlPayNoCapacityToast") + Verify the Send sheet is still on the recipient options (id "RecipientManual" is visible) and neither the LNURL amount screen (id "SendNumberField") nor a review screen (id "SendConfirm") was opened + Close the Send sheet and verify the wallet home screen is shown + +