From 1baa87350b5238f0e5f44720d2e671318265fff4 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Mon, 23 Mar 2026 17:33:44 +0100 Subject: [PATCH 1/4] formatting --- test/specs/mainnet/ln.e2e.ts | 10 +++++++--- test/specs/settings.e2e.ts | 1 - tools/seedkit/README.md | 16 +++++++--------- tools/seedkit/docs/ARCHITECTURE.md | 14 +++++++------- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/test/specs/mainnet/ln.e2e.ts b/test/specs/mainnet/ln.e2e.ts index aee7fe7..d170353 100644 --- a/test/specs/mainnet/ln.e2e.ts +++ b/test/specs/mainnet/ln.e2e.ts @@ -81,14 +81,18 @@ async function waitForPaymentResult(): Promise { console.info(`→ [LN] Waiting for payment result (timeout: ${PAYMENT_TIMEOUT_MS / 1000}s)...`); await browser.waitUntil( async () => { - const success = await elementById('SendSuccess').isDisplayed().catch(() => false); + const success = await elementById('SendSuccess') + .isDisplayed() + .catch(() => false); if (success) { console.info('→ [LN] Payment succeeded'); return true; } for (const toastId of ERROR_TOASTS) { - const visible = await elementById(toastId).isDisplayed().catch(() => false); + const visible = await elementById(toastId) + .isDisplayed() + .catch(() => false); if (visible) { throw new Error(`Payment failed with error toast: ${toastId}`); } @@ -100,7 +104,7 @@ async function waitForPaymentResult(): Promise { timeout: PAYMENT_TIMEOUT_MS, interval: 3_000, timeoutMsg: `Payment did not complete within ${PAYMENT_TIMEOUT_MS / 1000}s`, - }, + } ); } diff --git a/test/specs/settings.e2e.ts b/test/specs/settings.e2e.ts index 1fa2118..3a37bff 100644 --- a/test/specs/settings.e2e.ts +++ b/test/specs/settings.e2e.ts @@ -490,7 +490,6 @@ describe('@settings - Settings', () => { }); ciIt('@settings_12 - Can reset suggestions', async () => { - await elementById('TotalBalance-primary').waitForDisplayed(); await swipeFullScreen('up'); await elementById('SuggestionsWidget').waitForDisplayed(); diff --git a/tools/seedkit/README.md b/tools/seedkit/README.md index 83cf575..e03ee8d 100644 --- a/tools/seedkit/README.md +++ b/tools/seedkit/README.md @@ -53,13 +53,13 @@ seedkit preview --backend staging ## Scenarios -| Scenario | Description | -|----------|-------------| -| `first-time` | Clean wallet with one confirmed receive (50,000 sat) | +| Scenario | Description | +| ------------ | ----------------------------------------------------------- | +| `first-time` | Clean wallet with one confirmed receive (50,000 sat) | | `fragmented` | 18 small UTXOs (2,000-9,100 sat) for coin selection testing | -| `dust` | Tiny UTXOs at spendability edge cases (330-1,000 sat) | -| `merchant` | 12 inbound payments across multiple blocks | -| `savings` | Single large UTXO (1,000,000 sat) | +| `dust` | Tiny UTXOs at spendability edge cases (330-1,000 sat) | +| `merchant` | 12 inbound payments across multiple blocks | +| `savings` | Single large UTXO (1,000,000 sat) | ## Backends @@ -86,9 +86,7 @@ When used with `--output json`, the `run` command outputs structured JSON for pr { "scenario": "first-time", "mnemonic": "word1 word2 ...", - "addresses": [ - {"index": 0, "address": "bcrt1q...", "amountSat": 50000, "confirmed": true} - ], + "addresses": [{ "index": 0, "address": "bcrt1q...", "amountSat": 50000, "confirmed": true }], "totalSat": 50000, "utxoCount": 1, "blocksMined": 1 diff --git a/tools/seedkit/docs/ARCHITECTURE.md b/tools/seedkit/docs/ARCHITECTURE.md index 83bb165..456d4bb 100644 --- a/tools/seedkit/docs/ARCHITECTURE.md +++ b/tools/seedkit/docs/ARCHITECTURE.md @@ -42,13 +42,13 @@ Connection details from existing infra: These scenarios only need deposit + mine, no outgoing tx signing: -| Scenario | What it creates | Addresses | Deposits | -|----------|----------------|-----------|----------| -| **first-time** | Clean wallet, one confirmed receive | 1 | 1 x 50,000 sat, mine 1 block | -| **fragmented** | Many small UTXOs for coin selection testing | 18 | 18 x 2,000-9,100 sat each, mine | -| **dust** | Tiny UTXOs at spendability edge | 5 | Mix of 330, 546, 600, 800, 1000 sat, mine | -| **merchant** | Many inbound payments, rich history | 12 | 12 varied amounts (2k-85k sat), mined across multiple blocks | -| **savings** | Large single UTXO, simple balance | 1 | 1 x 1,000,000 sat, mine | +| Scenario | What it creates | Addresses | Deposits | +| -------------- | ------------------------------------------- | --------- | ------------------------------------------------------------ | +| **first-time** | Clean wallet, one confirmed receive | 1 | 1 x 50,000 sat, mine 1 block | +| **fragmented** | Many small UTXOs for coin selection testing | 18 | 18 x 2,000-9,100 sat each, mine | +| **dust** | Tiny UTXOs at spendability edge | 5 | Mix of 330, 546, 600, 800, 1000 sat, mine | +| **merchant** | Many inbound payments, rich history | 12 | 12 varied amounts (2k-85k sat), mined across multiple blocks | +| **savings** | Large single UTXO, simple balance | 1 | 1 x 1,000,000 sat, mine | ### Future scenarios (require transaction building - Phase 2) From 6ecf4f35ebd99f1ac3b60cc25ecd116973aab24e Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Tue, 24 Mar 2026 16:09:40 +0100 Subject: [PATCH 2/4] explicitely grant IOS Camera Permission --- test/helpers/setup.ts | 40 +++++++++++++++++++++++++++++++++++++ test/specs/migration.e2e.ts | 5 +++++ wdio.conf.ts | 7 +++++++ 3 files changed, 52 insertions(+) diff --git a/test/helpers/setup.ts b/test/helpers/setup.ts index 911d3e1..77b53c0 100644 --- a/test/helpers/setup.ts +++ b/test/helpers/setup.ts @@ -4,6 +4,44 @@ import path from 'node:path'; import { sleep } from './actions'; import { getAppId, getAppPath } from './constants'; +function getIosSimulatorUdidForSimctl(): string { + try { + let udid = + (driver.capabilities as Record)['appium:udid']?.toString() ?? + (driver.capabilities as Record).udid?.toString() ?? + (driver.capabilities as Record).deviceUDID?.toString() ?? + process.env.SIMULATOR_UDID ?? + ''; + if (udid && udid !== 'auto') return udid; + } catch { + /* ignore */ + } + try { + const line = execSync('xcrun simctl list devices booted', { encoding: 'utf8' }); + const match = line.match(/\(([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})\)/i); + if (match) return match[1] ?? ''; + } catch { + /* ignore */ + } + return ''; +} + +export function grantIOSCameraPermission(appIdParam?: string) { + if (typeof driver === 'undefined' || !driver.isIOS) return; + const appId = appIdParam ?? getAppId(); + const udid = getIosSimulatorUdidForSimctl(); + if (!udid) { + console.warn('⚠ grantIOSCameraPermission: could not resolve simulator UDID'); + return; + } + try { + execSync(`xcrun simctl privacy "${udid}" grant camera "${appId}"`, { stdio: 'ignore' }); + console.info(`→ Granted iOS camera permission for '${appId}' (simulator ${udid})`); + } catch (error) { + console.warn('⚠ grantIOSCameraPermission failed', error); + } +} + export async function launchFreshApp() { const appId = getAppId(); @@ -23,6 +61,7 @@ export async function reinstallApp() { await driver.removeApp(appId); resetBootedIOSKeychain(); await driver.installApp(appPath); + grantIOSCameraPermission(appId); await driver.activateApp(appId); } @@ -52,6 +91,7 @@ export async function reinstallAppFromPath(appPath: string, appId: string = getA await driver.removeApp(appId); resetBootedIOSKeychain(); await driver.installApp(appPath); + grantIOSCameraPermission(appId); await driver.activateApp(appId); } diff --git a/test/specs/migration.e2e.ts b/test/specs/migration.e2e.ts index 30692a0..96f5581 100644 --- a/test/specs/migration.e2e.ts +++ b/test/specs/migration.e2e.ts @@ -26,6 +26,7 @@ import { ciIt } from '../helpers/suite'; import { getNativeAppPath, getRnAppPath, + grantIOSCameraPermission, reinstallAppFromPath, resetBootedIOSKeychain, } from '../helpers/setup'; @@ -170,6 +171,7 @@ describe('@migration - Migration from legacy RN app to native app', () => { // Install native app console.info(`→ Installing native app from: ${getNativeAppPath()}`); await driver.installApp(getNativeAppPath()); + grantIOSCameraPermission(); await driver.activateApp(getAppId()); // Restore wallet with mnemonic (uses custom flow to handle backup sheet) @@ -194,6 +196,7 @@ describe('@migration - Migration from legacy RN app to native app', () => { // Install native app ON TOP of RN (upgrade) console.info(`→ Installing native app on top of RN: ${getNativeAppPath()}`); await driver.installApp(getNativeAppPath()); + grantIOSCameraPermission(); await driver.activateApp(getAppId()); // Handle migration flow @@ -213,6 +216,7 @@ describe('@migration - Migration from legacy RN app to native app', () => { // Install native app ON TOP of RN (upgrade) console.info(`→ Installing native app on top of RN: ${getNativeAppPath()}`); await driver.installApp(getNativeAppPath()); + grantIOSCameraPermission(); await driver.activateApp(getAppId()); // Handle migration flow @@ -234,6 +238,7 @@ describe('@migration - Migration from legacy RN app to native app', () => { // Install native app ON TOP of RN (upgrade) console.info(`→ Installing native app on top of RN: ${getNativeAppPath()}`); await driver.installApp(getNativeAppPath()); + grantIOSCameraPermission(); await driver.activateApp(getAppId()); // Handle migration flow diff --git a/wdio.conf.ts b/wdio.conf.ts index 6ab7aec..8140efe 100644 --- a/wdio.conf.ts +++ b/wdio.conf.ts @@ -1,6 +1,8 @@ import path from 'node:path'; import fs from 'node:fs'; +import { grantIOSCameraPermission } from './test/helpers/setup'; + const isAndroid = process.env.PLATFORM === 'android'; const iosDeviceName = process.env.SIMULATOR_NAME || 'iPhone 17'; const iosPlatformVersion = process.env.SIMULATOR_OS_VERSION || '26.0.1'; @@ -334,6 +336,11 @@ export const config: WebdriverIO.Config = { */ // afterAssertion: function(params) { // } + before: async function () { + if (!isAndroid) { + grantIOSCameraPermission(); + } + }, beforeTest: async function (test) { if (process.env.RECORD_VIDEO === 'true') { const recordingOptions = isAndroid From eb7442a9598791020a64124f4f403311e66f46a9 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Wed, 25 Mar 2026 12:32:24 +0100 Subject: [PATCH 3/4] adjust adding tag on send --- test/helpers/actions.ts | 11 ++++++++++- test/specs/lightning.e2e.ts | 5 ++--- test/specs/onchain.e2e.ts | 6 ++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 2cafe2b..14ef823 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -419,6 +419,15 @@ export async function typeText(testId: string, text: string) { await el.setValue(text); } +export async function addSendTag(tag: string) { + if (driver.isIOS) { + await tap('SendConfirmToggleDetails'); + } + await tap('TagsAddSend'); + await typeText('TagInputSend', tag); + await tap('SendTagsSubmit'); +} + export async function enterAmount(amountSats: number) { for (const digit of `${amountSats}`.split('')) { await tap(`N${digit}`); @@ -1389,7 +1398,7 @@ export async function typeAddressAndVerifyContinue({ export async function enterAddress( address: string, - { acceptCameraPermission = true, addressTimeout = 30_000 } = {}, + { acceptCameraPermission = true, addressTimeout = 30_000 } = {} ) { await tap('Send'); await sleep(700); diff --git a/test/specs/lightning.e2e.ts b/test/specs/lightning.e2e.ts index 54a9593..fc58e7c 100644 --- a/test/specs/lightning.e2e.ts +++ b/test/specs/lightning.e2e.ts @@ -22,6 +22,7 @@ import { doNavigationClose, dismissBackgroundPaymentsTimedSheet, acknowledgeReceivedPayment, + addSendTag, waitForBackup, waitForToast, } from '../helpers/actions'; @@ -174,9 +175,7 @@ describe('@lightning - Lightning', () => { await expect(reviewAmt).toHaveText('1 000'); await console.info('I cannot edit the amount on Review screen'); await tap('ReviewAmount-primary'); - await tap('TagsAddSend'); - await typeText('TagInputSend', 'stag'); - await tap('SendTagsSubmit'); + await addSendTag('stag'); await sleep(500); // wait for keyboard to close await dragOnElement('GRAB', 'right', 0.95); // Swipe to confirm await elementById('SendSuccess').waitForDisplayed(); diff --git a/test/specs/onchain.e2e.ts b/test/specs/onchain.e2e.ts index 1fbb17c..71a30de 100644 --- a/test/specs/onchain.e2e.ts +++ b/test/specs/onchain.e2e.ts @@ -21,6 +21,7 @@ import { handleOver50PercentAlert, handleOver100Alert, acknowledgeReceivedPayment, + addSendTag, enterAmount, formatSats, expectTotalBalance, @@ -151,10 +152,7 @@ describe('@onchain - Onchain', () => { await tap('ContinueAmount'); // Review & Send - await elementById('TagsAddSend').waitForDisplayed(); - await tap('TagsAddSend'); - await typeText('TagInputSend', 'stag'); - await elementByText('Add', 'exact').click(); + await addSendTag('stag'); await dragOnElement('GRAB', 'right', 0.95); await sleep(1000); From 1e110584838cf7073aceba4cd3a46b5db50589d0 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Wed, 25 Mar 2026 14:03:00 +0100 Subject: [PATCH 4/4] extract editRecipientAddress --- test/helpers/actions.ts | 22 ++++++++++++++++++++++ test/specs/send.e2e.ts | 22 +++------------------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index 14ef823..06ad74c 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -1382,6 +1382,28 @@ export async function typeRecipientInput( } } +export async function editRecipientAddress(address: string) { + if (driver.isIOS) { + await tap('SendConfirmToggleDetails'); + } + await tap('ReviewUri'); + await sleep(2000); + await elementById('RecipientInput').waitForDisplayed(); + await sleep(500); + try { + console.info('Typing on the RecipientInput...'); + console.info({ address }); + await typeRecipientInput(address); + await elementById('AddressContinue').waitForEnabled(); + await sleep(500); + } catch { + console.warn('Typing on the RecipientInput failed, trying again...'); + await typeRecipientInput(address); + await elementById('AddressContinue').waitForEnabled(); + await sleep(500); + } +} + export async function typeAddressAndVerifyContinue({ address, reverse = false, diff --git a/test/specs/send.e2e.ts b/test/specs/send.e2e.ts index 08293ba..ba2f3b9 100644 --- a/test/specs/send.e2e.ts +++ b/test/specs/send.e2e.ts @@ -22,11 +22,12 @@ import { handleAndroidAlert, dismissBackgroundPaymentsTimedSheet, acknowledgeReceivedPayment, + editRecipientAddress, typeRecipientInput, + tap, } from '../helpers/actions'; import { lndConfig } from '../helpers/constants'; import { reinstallApp } from '../helpers/setup'; -import { confirmInputOnKeyboard, tap, typeText } from '../helpers/actions'; import { connectToLND, getLDKNodeID, @@ -284,24 +285,7 @@ describe('@send - Send', () => { const reviewAmt = await elementByIdWithin('ReviewAmount-primary', 'MoneyText'); await reviewAmt.waitForDisplayed(); await expect(reviewAmt).toHaveText('2 000'); - await tap('ReviewUri'); - await sleep(2000); - await elementById('RecipientInput').waitForDisplayed(); - await sleep(500); - try { - console.info('Typing on the RecipientInput...'); - console.info({ onchainAddress }); - await typeText('RecipientInput', onchainAddress); - await confirmInputOnKeyboard(); - await elementById('AddressContinue').waitForEnabled(); - await sleep(500); - } catch { - console.warn('Typing on the RecipientInput failed, trying again...'); - await typeText('RecipientInput', onchainAddress); - await confirmInputOnKeyboard(); - await elementById('AddressContinue').waitForEnabled(); - await sleep(500); - } + await editRecipientAddress(onchainAddress); await tap('AddressContinue'); await elementById('AssetButton-savings').waitForDisplayed(); await tap('N2');