diff --git a/test/helpers/hardware-wallet.ts b/test/helpers/hardware-wallet.ts index f7339ba..e5aaead 100644 --- a/test/helpers/hardware-wallet.ts +++ b/test/helpers/hardware-wallet.ts @@ -4,15 +4,20 @@ import path from 'node:path'; import { acknowledgeReceivedPaymentIfPresent, + addSendTag, doNavigationClose, + dragOnElement, elementById, elementByText, + enterAddress, enterAmount, expectBalanceWithWait, expectTextWithin, expectText, formatSats, + getAccessibleText, getAmountUnder, + getTextUnder, sleep, tap, tapFirstByAccessibilityIdPrefix, @@ -207,6 +212,77 @@ 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); + 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 }); + if (tag) { + await addSendTag(tag); + await sleep(500); + } + 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 +311,11 @@ 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 +357,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/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/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/hardware-wallet.e2e.ts b/test/specs/hardware-wallet.e2e.ts index 26fd195..dbe6efc 100644 --- a/test/specs/hardware-wallet.e2e.ts +++ b/test/specs/hardware-wallet.e2e.ts @@ -16,16 +16,18 @@ import { expectHardwareWalletInSettings, expectHardwareWalletOnHome, expectHardwareWalletReceivedActivity, + expectHardwareWalletSentActivity, fundHardwareWalletAndAcknowledge, 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 +112,32 @@ 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 tag = 'hwsend'; + 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, + amountSats: sendSats, + tag, + }); + await expectHardwareWalletSentActivity(tag); + await doNavigationClose(); + await expectHardwareWalletBalance(fundingSats, { condition: 'lt' }); + }); }); 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/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'; 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,