From 0f559924ba3feff63cba8ca19afb5a84718d3997 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Fri, 28 Aug 2026 12:04:57 +0200 Subject: [PATCH 1/5] test: initial hw send test --- test/helpers/hardware-wallet.ts | 67 +++++++++++++++++++++++++++++-- test/specs/hardware-wallet.e2e.ts | 20 ++++++++- test/specs/paykit.e2e.ts | 1 - 3 files changed, 82 insertions(+), 6 deletions(-) diff --git a/test/helpers/hardware-wallet.ts b/test/helpers/hardware-wallet.ts index f7339ba..0640bd3 100644 --- a/test/helpers/hardware-wallet.ts +++ b/test/helpers/hardware-wallet.ts @@ -5,14 +5,18 @@ import path from 'node:path'; import { acknowledgeReceivedPaymentIfPresent, doNavigationClose, + dragOnElement, elementById, elementByText, + enterAddress, enterAmount, expectBalanceWithWait, expectTextWithin, expectText, formatSats, + getAccessibleText, getAmountUnder, + getTextUnder, sleep, tap, tapFirstByAccessibilityIdPrefix, @@ -207,6 +211,61 @@ export async function expectHardwareWalletReceivedActivity(sats: number) { await expectTextWithin('Activity-1', formatSats(sats)); } +export async function sendOnchainFromHardwareWallet({ + walletLabel, + address, + amountSats, +}: { + walletLabel: string; + address: string; + amountSats: number; +}) { + await doNavigationClose(); + await enterAddress(address); + await elementById('ContinueAmount').waitForDisplayed({ timeout: 60_000 }); + await selectHardwareFundingSource(walletLabel); + await enterAmount(amountSats); + await elementById('ContinueAmount').waitForEnabled({ timeout: 60_000 }); + await tap('ContinueAmount'); + await elementById('GRAB').waitForDisplayed({ timeout: 120_000 }); + await dragOnElement('GRAB', 'right', 0.95); + await elementById('HardwareSendOpenTrezorConnect').waitForDisplayed({ timeout: 120_000 }); + await tap('HardwareSendOpenTrezorConnect'); + await approveTrezorPromptsUntil(['SendSuccess']); + await elementById('SendSuccess').waitForDisplayed(); + await tap('Close'); +} + +async function selectHardwareFundingSource(walletLabel: string) { + const switchButton = elementById('AssetButton-switch'); + await switchButton.waitForDisplayed({ timeout: 30_000 }); + + const wanted = walletLabel.toLowerCase(); + const started = Date.now(); + const timeout = 60_000; + while (Date.now() - started < timeout) { + const label = await fundingSourceLabel(); + if (label.toLowerCase().includes(wanted)) { + return; + } + await tap('AssetButton-switch'); + await sleep(500); + } + + throw new Error(`Timed out selecting hardware funding source '${walletLabel}'`); +} + +async function fundingSourceLabel(): Promise { + if (driver.isAndroid) { + try { + return (await getTextUnder('AssetButton-switch', 'first')).trim(); + } catch { + return ''; + } + } + return getAccessibleText(elementById('AssetButton-switch')); +} + export async function transferHardwareWalletToSpending({ amountSats, waitForSync, @@ -235,7 +294,7 @@ export async function transferHardwareWalletToSpending({ await tap('HardwareTransferAmountContinue'); await elementById('HardwareTransferSign').waitForDisplayed({ timeout: 120_000 }); await tap('HardwareTransferOpenTrezorConnect'); - await approveTrezorPromptsUntilTransferProgress(); + await approveTrezorPromptsUntil(['HardwareTransferSigned', 'LightningSettingUp', 'TransferSuccess']); await waitForHardwareTransferProgress(); if (getBackend() === 'local') { // Local backend does not have Blocktank, @@ -277,19 +336,19 @@ export async function removeHardwareWalletFromSettings(label: string) { await expectHardwareWalletInSettings(label, { visible: false }); } -async function approveTrezorPromptsUntilTransferProgress() { +async function approveTrezorPromptsUntil(testIds: string[]) { const started = Date.now(); const timeout = 180_000; while (Date.now() - started < timeout) { - if (await isAnyDisplayed(['HardwareTransferSigned', 'LightningSettingUp', 'TransferSuccess'])) { + if (await isAnyDisplayed(testIds)) { return; } pressTrezorYes(); await sleep(500); } - throw new Error('Timed out waiting for hardware transfer signing progress'); + throw new Error(`Timed out waiting for Trezor approval (${testIds.join(', ')})`); } async function waitForHardwareTransferProgress() { diff --git a/test/specs/hardware-wallet.e2e.ts b/test/specs/hardware-wallet.e2e.ts index 26fd195..56125ad 100644 --- a/test/specs/hardware-wallet.e2e.ts +++ b/test/specs/hardware-wallet.e2e.ts @@ -20,12 +20,13 @@ import { openHardwareWalletSettings, removeHardwareWalletFromSettings, renameHardwareWalletFromSettings, + sendOnchainFromHardwareWallet, startHardwareWalletFlowFromSuggestion, stopTrezorEmulator, transferHardwareWalletToSpending, type TrezorEmulatorFixture, } from '../helpers/hardware-wallet'; -import { ensureLocalFunds, getBackend } from '../helpers/regtest'; +import { ensureLocalFunds, getBackend, getExternalAddress } from '../helpers/regtest'; import { reinstallApp } from '../helpers/setup'; import { ciIt } from '../helpers/suite'; @@ -110,4 +111,21 @@ describe('@hardware_wallet - Hardware Wallet', () => { await expectSpendingBalance(0, { condition: 'gt', timeout: 60_000 }); } }); + + ciIt('@hardware_wallet_4 - Can send onchain from hardware wallet', async () => { + const fundingSats = 100_000; + const sendSats = 20_000; + const address = await getExternalAddress(); + + await connectHardwareWalletFromSettings(walletLabel); + await fundHardwareWalletAndAcknowledge(trezorFixture, { sats: fundingSats }); + await expectHardwareWalletBalance(fundingSats); + await sendOnchainFromHardwareWallet({ + walletLabel, + address, + amountSats: sendSats, + }); + await doNavigationClose(); + await expectHardwareWalletBalance(fundingSats, { condition: 'lt' }); + }); }); diff --git a/test/specs/paykit.e2e.ts b/test/specs/paykit.e2e.ts index b3966a6..71a365d 100644 --- a/test/specs/paykit.e2e.ts +++ b/test/specs/paykit.e2e.ts @@ -18,7 +18,6 @@ import { cleanupProfile, createProfile, deleteProfile, - discardAddContactRoute, verifyAddContactRoute, verifyContactRowDisplayed, } from '../helpers/profile'; From 1bd7ba2912204b83b59a406f02961faccdabeffa Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Fri, 28 Aug 2026 12:26:13 +0200 Subject: [PATCH 2/5] test: include savings --- test/helpers/navigation.ts | 1 + test/specs/hardware-wallet.e2e.ts | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/test/helpers/navigation.ts b/test/helpers/navigation.ts index fab9710..5af76a7 100644 --- a/test/helpers/navigation.ts +++ b/test/helpers/navigation.ts @@ -7,6 +7,7 @@ export type SettingsTab = 'general' | 'security' | 'advanced'; * General is the default tab so no extra tap is needed for it. */ export async function openSettings(tab: SettingsTab = 'general') { + await sleep(500); await tap('HeaderMenu'); await tap('DrawerSettings'); await sleep(500); diff --git a/test/specs/hardware-wallet.e2e.ts b/test/specs/hardware-wallet.e2e.ts index 56125ad..e7ebb01 100644 --- a/test/specs/hardware-wallet.e2e.ts +++ b/test/specs/hardware-wallet.e2e.ts @@ -117,9 +117,17 @@ describe('@hardware_wallet - Hardware Wallet', () => { const sendSats = 20_000; const address = await getExternalAddress(); + // receive some on savings account first + await receiveOnchainFunds({ sats: fundingSats, expectHighBalanceWarning: false }); + await expectSavingsBalance(fundingSats); + await expectTotalBalance(fundingSats); + await expectSpendingBalance(0); + + // connect hardware wallet and send onchain await connectHardwareWalletFromSettings(walletLabel); await fundHardwareWalletAndAcknowledge(trezorFixture, { sats: fundingSats }); await expectHardwareWalletBalance(fundingSats); + await expectTotalBalance(fundingSats * 2); await sendOnchainFromHardwareWallet({ walletLabel, address, From a79cc97782d2a0f6ec2be826b50e072d81ded34d Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Fri, 28 Aug 2026 12:55:33 +0200 Subject: [PATCH 3/5] test: add tag to hwsend --- test/helpers/hardware-wallet.ts | 17 +++++++++++++++++ test/specs/hardware-wallet.e2e.ts | 4 ++++ 2 files changed, 21 insertions(+) diff --git a/test/helpers/hardware-wallet.ts b/test/helpers/hardware-wallet.ts index 0640bd3..93ce05b 100644 --- a/test/helpers/hardware-wallet.ts +++ b/test/helpers/hardware-wallet.ts @@ -4,6 +4,7 @@ import path from 'node:path'; import { acknowledgeReceivedPaymentIfPresent, + addSendTag, doNavigationClose, dragOnElement, elementById, @@ -211,14 +212,26 @@ export async function expectHardwareWalletReceivedActivity(sats: number) { await expectTextWithin('Activity-1', formatSats(sats)); } +export async function expectHardwareWalletSentActivity(tag: string) { + await doNavigationClose(); + await tap('ActivityHardware'); + await elementById('HardwareWalletScreen').waitForDisplayed(); + await elementById('Activity-1').waitForDisplayed(); + await expectTextWithin('Activity-1', '-'); + await tap('Activity-1'); + await elementById(`Tag-${tag}-delete`).waitForDisplayed(); +} + export async function sendOnchainFromHardwareWallet({ walletLabel, address, amountSats, + tag, }: { walletLabel: string; address: string; amountSats: number; + tag?: string; }) { await doNavigationClose(); await enterAddress(address); @@ -228,6 +241,10 @@ export async function sendOnchainFromHardwareWallet({ await elementById('ContinueAmount').waitForEnabled({ timeout: 60_000 }); await tap('ContinueAmount'); await elementById('GRAB').waitForDisplayed({ timeout: 120_000 }); + if (tag) { + await addSendTag(tag); + await sleep(500); + } await dragOnElement('GRAB', 'right', 0.95); await elementById('HardwareSendOpenTrezorConnect').waitForDisplayed({ timeout: 120_000 }); await tap('HardwareSendOpenTrezorConnect'); diff --git a/test/specs/hardware-wallet.e2e.ts b/test/specs/hardware-wallet.e2e.ts index e7ebb01..dbe6efc 100644 --- a/test/specs/hardware-wallet.e2e.ts +++ b/test/specs/hardware-wallet.e2e.ts @@ -16,6 +16,7 @@ import { expectHardwareWalletInSettings, expectHardwareWalletOnHome, expectHardwareWalletReceivedActivity, + expectHardwareWalletSentActivity, fundHardwareWalletAndAcknowledge, openHardwareWalletSettings, removeHardwareWalletFromSettings, @@ -115,6 +116,7 @@ describe('@hardware_wallet - Hardware Wallet', () => { ciIt('@hardware_wallet_4 - Can send onchain from hardware wallet', async () => { const fundingSats = 100_000; const sendSats = 20_000; + const tag = 'hwsend'; const address = await getExternalAddress(); // receive some on savings account first @@ -132,7 +134,9 @@ describe('@hardware_wallet - Hardware Wallet', () => { walletLabel, address, amountSats: sendSats, + tag, }); + await expectHardwareWalletSentActivity(tag); await doNavigationClose(); await expectHardwareWalletBalance(fundingSats, { condition: 'lt' }); }); From 3da364391ee20288dfad7c762f70548f730ae12f Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Fri, 28 Aug 2026 13:05:48 +0200 Subject: [PATCH 4/5] formatting --- test/helpers/hardware-wallet.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/helpers/hardware-wallet.ts b/test/helpers/hardware-wallet.ts index 93ce05b..e5aaead 100644 --- a/test/helpers/hardware-wallet.ts +++ b/test/helpers/hardware-wallet.ts @@ -311,7 +311,11 @@ export async function transferHardwareWalletToSpending({ await tap('HardwareTransferAmountContinue'); await elementById('HardwareTransferSign').waitForDisplayed({ timeout: 120_000 }); await tap('HardwareTransferOpenTrezorConnect'); - await approveTrezorPromptsUntil(['HardwareTransferSigned', 'LightningSettingUp', 'TransferSuccess']); + await approveTrezorPromptsUntil([ + 'HardwareTransferSigned', + 'LightningSettingUp', + 'TransferSuccess', + ]); await waitForHardwareTransferProgress(); if (getBackend() === 'local') { // Local backend does not have Blocktank, From 2b04d6dc709e2ea55ac43f996131fee9630ca033 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Fri, 28 Aug 2026 13:11:04 +0200 Subject: [PATCH 5/5] test: cleanup --- test/specs/boost.e2e.ts | 1 - test/specs/migration.e2e.ts | 2 +- test/specs/receive.e2e.ts | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/specs/boost.e2e.ts b/test/specs/boost.e2e.ts index b1113dd..e7abf5a 100644 --- a/test/specs/boost.e2e.ts +++ b/test/specs/boost.e2e.ts @@ -6,7 +6,6 @@ import { receiveOnchainFunds, expectText, dragOnElement, - swipeFullScreen, expectTextWithin, elementByIdWithin, getTextUnder, diff --git a/test/specs/migration.e2e.ts b/test/specs/migration.e2e.ts index 6dc165f..f9401a1 100644 --- a/test/specs/migration.e2e.ts +++ b/test/specs/migration.e2e.ts @@ -191,7 +191,7 @@ describe('@migration - Migration from legacy RN app to native app', () => { // -------------------------------------------------------------------------- ciIt('@migration_2 - Install native on top of RN (upgrade)', async () => { // Setup wallet in RN app - const { mnemonic, balance } = await setupLegacyWallet({ returnSeed: true }); + const { balance } = await setupLegacyWallet(); // Install native app ON TOP of RN (upgrade) console.info(`→ Installing native app on top of RN: ${getNativeAppPath()}`); diff --git a/test/specs/receive.e2e.ts b/test/specs/receive.e2e.ts index 971df5f..ea297f2 100644 --- a/test/specs/receive.e2e.ts +++ b/test/specs/receive.e2e.ts @@ -1,7 +1,6 @@ import { completeOnboarding, confirmInputOnKeyboard, - dragOnElement, elementById, expectText, expectTextWithin,