From 2dfe016e0eed28ad0e55d2c665d21de532e75a59 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 20 Sep 2026 15:55:15 +0000 Subject: [PATCH 1/2] test: assert transfer_1 rows on Home ActivityShort iOS staging @transfer_1 raced a delayed Background Payments sheet: tapping ActivitySavings to read Activity-* either timed out under the sheet or landed back on Home, where the transfers were already visible as ActivityShort rows. Wait for those Home rows after each buy and retry the Savings tap only while Home is actually showing. Co-authored-by: piotr-iohk --- test/specs/transfer.e2e.ts | 127 ++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 57 deletions(-) diff --git a/test/specs/transfer.e2e.ts b/test/specs/transfer.e2e.ts index 8475730..19c8a6e 100644 --- a/test/specs/transfer.e2e.ts +++ b/test/specs/transfer.e2e.ts @@ -23,8 +23,10 @@ import { expectNoTextWithin, enterAmount, expectSavingsBalance, + getAccessibleText, getSpendingBalance, getAmountUnder, + getTextUnder, tryDismissBackgroundPaymentsIfVisible, tryDismissQuickPayIntroIfVisible, } from '../helpers/actions'; @@ -63,11 +65,24 @@ async function dismissHomeSheetsIfPresent() { } async function openTransferToSpending() { - await dismissHomeSheetsIfPresent(); - await tap('ActivitySavings'); await browser.waitUntil( async () => { await dismissHomeSheetsIfPresent(); + if (await isDisplayed('TransferToSpending')) { + return true; + } + // Only tap from Home. A delayed Background Payments sheet can cover + // ActivitySavings after waitForHome; keep dismissing and retry. + const onHome = + (await isDisplayed('ActivitySavings')) && (await isDisplayed('ActivitySpending')); + if (!onHome) { + return false; + } + try { + await tap('ActivitySavings', { timeout: 5_000 }); + } catch { + return false; + } return isDisplayed('TransferToSpending'); }, { @@ -150,22 +165,6 @@ async function waitForHome(timeout = 60_000) { ); } -async function returnHomeFromSavings() { - for (let attempt = 1; attempt <= 3; attempt += 1) { - await dismissHomeSheetsIfPresent(); - await tap('NavigationBack'); - try { - await waitForHome(5_000); - return; - } catch (error) { - if (attempt === 3) { - throw error; - } - console.info(`→ Savings back navigation did not land on Home (attempt ${attempt})`); - } - } -} - async function isProcessingPaymentDisplayed(): Promise { return elementByText('Processing payment', 'exact') .isDisplayed() @@ -187,19 +186,59 @@ async function expectProcessingOrUsableChannel() { await elementById('IsUsableYes').waitForDisplayed(); } +async function activityShortShowsTransfer(shortId: string): Promise { + const row = elementById(shortId); + if (!(await row.isDisplayed().catch(() => false))) { + return false; + } + + const snippets: string[] = []; + if (driver.isIOS) { + for (const attribute of ['label', 'value'] as const) { + const value = await row.getAttribute(attribute).catch(() => ''); + if (typeof value === 'string' && value.length > 0) { + snippets.push(value); + } + } + } else { + snippets.push(await getAccessibleText(row)); + try { + snippets.push(await getTextUnder(shortId, 'first'), await getTextUnder(shortId, 'last')); + } catch { + // Row descendants can still be attaching. + } + } + + const haystack = snippets.join(' '); + return haystack.includes('Transfer') && haystack.includes('-'); +} + /** - * Activity-1 starts as the on-chain receive until Transfer is inserted above it - * (same race as @transfer_max ActivityShort-0/1). Wait for Transfer labels at - * 60s instead of assuming Activity-2/3 exist at the default 30s. + * After each Blocktank buy, Home ActivityShort inserts a Transfer row at the top. + * Do not open Savings to read Activity-*: a delayed Background Payments sheet can + * cover ActivitySavings, and dismissing that sheet leaves Home — where the + * transfers are already visible as ActivityShort-*. */ -async function expectSavingsTransferRows(transferCount: 1 | 2) { - const requiredRows = - transferCount === 1 ? ['Activity-1', 'Activity-2'] : ['Activity-1', 'Activity-2', 'Activity-3']; +async function expectHomeTransferRows(transferCount: 1 | 2) { + let transferRowIds: readonly string[]; + switch (transferCount) { + case 1: + transferRowIds = ['ActivityShort-0']; + break; + case 2: + transferRowIds = ['ActivityShort-0', 'ActivityShort-1']; + break; + default: { + const _exhaustive: never = transferCount; + throw new Error(`Unexpected transferCount: ${_exhaustive}`); + } + } + await browser.waitUntil( async () => { await dismissHomeSheetsIfPresent(); - for (const rowId of requiredRows) { - if (!(await isDisplayed(rowId))) { + for (const rowId of transferRowIds) { + if (!(await activityShortShowsTransfer(rowId))) { return false; } } @@ -208,26 +247,9 @@ async function expectSavingsTransferRows(transferCount: 1 | 2) { { timeout: 60_000, interval: 1_000, - timeoutMsg: `Savings did not show ${transferCount} transfer row(s)`, + timeoutMsg: `Home did not show ${transferCount} transfer row(s)`, } ); - - switch (transferCount) { - case 1: - await expectTextWithin('Activity-1', 'Transfer', { timeout: 60_000 }); - await expectTextWithin('Activity-1', '-'); - return; - case 2: - await expectTextWithin('Activity-1', 'Transfer', { timeout: 60_000 }); - await expectTextWithin('Activity-1', '-'); - await expectTextWithin('Activity-2', 'Transfer', { timeout: 60_000 }); - await expectTextWithin('Activity-2', '-'); - return; - default: { - const _exhaustive: never = transferCount; - throw new Error(`Unexpected transferCount: ${_exhaustive}`); - } - } } async function confirmSpendingTransfer() { @@ -370,10 +392,7 @@ describe('@transfer - Transfer', () => { await expectText('200 000', { strategy: 'contains' }); await tap('LiquidityContinue'); await confirmSpendingTransfer(); - - await tap('ActivitySavings'); - await expectSavingsTransferRows(1); - await returnHomeFromSavings(); + await expectHomeTransferRows(1); await settleAndExpectSpendingBalance(200000, async () => electrum?.waitForSync()); @@ -389,10 +408,7 @@ describe('@transfer - Transfer', () => { await expectTextWithin('SpendingConfirmChannel', '100 000'); await expectTextWithin('SpendingConfirmChannel', '150 000'); await confirmSpendingTransfer(); - - await tap('ActivitySavings'); - await expectSavingsTransferRows(2); - await returnHomeFromSavings(); + await expectHomeTransferRows(2); // Both channel funding transactions must settle into usable spending balance. await settleAndExpectSpendingBalance(300000, async () => electrum?.waitForSync()); @@ -406,11 +422,8 @@ describe('@transfer - Transfer', () => { await expectProcessingOrUsableChannel(); await doNavigationClose(); - // Home shows both completed transfers. - await elementById('ActivityShort-0').waitForDisplayed(); - await expectTextWithin('ActivityShort-0', 'Transfer'); - await elementById('ActivityShort-1').waitForDisplayed(); - await expectTextWithin('ActivityShort-1', 'Transfer'); + // Home still shows both completed transfers after the channel inspection. + await expectHomeTransferRows(2); } ); From ce0113d424d400c84e35ace105496285daea9eb4 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Mon, 21 Sep 2026 19:59:41 +0200 Subject: [PATCH 2/2] test: fix Android Home transfer row assert via expectTextWithin ActivityShort amount minus is a middle TextView on Android; first/last probes never saw '-'. Poll with all-descendant expectTextWithin (same pattern as @transfer_max) until Transfer rows replace the deposit row. --- test/specs/transfer.e2e.ts | 43 ++++++++++++-------------------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/test/specs/transfer.e2e.ts b/test/specs/transfer.e2e.ts index 09cc55e..7b59df0 100644 --- a/test/specs/transfer.e2e.ts +++ b/test/specs/transfer.e2e.ts @@ -23,10 +23,8 @@ import { expectNoTextWithin, enterAmount, expectSavingsBalance, - getAccessibleText, getSpendingBalance, getAmountUnder, - getTextUnder, tryDismissBackgroundPaymentsIfVisible, tryDismissQuickPayIntroIfVisible, } from '../helpers/actions'; @@ -186,38 +184,14 @@ async function expectProcessingOrUsableChannel() { await elementById('IsUsableYes').waitForDisplayed(); } -async function activityShortShowsTransfer(shortId: string): Promise { - const row = elementById(shortId); - if (!(await row.isDisplayed().catch(() => false))) { - return false; - } - - const snippets: string[] = []; - if (driver.isIOS) { - for (const attribute of ['label', 'value'] as const) { - const value = await row.getAttribute(attribute).catch(() => ''); - if (typeof value === 'string' && value.length > 0) { - snippets.push(value); - } - } - } else { - snippets.push(await getAccessibleText(row)); - try { - snippets.push(await getTextUnder(shortId, 'first'), await getTextUnder(shortId, 'last')); - } catch { - // Row descendants can still be attaching. - } - } - - const haystack = snippets.join(' '); - return haystack.includes('Transfer') && haystack.includes('-'); -} - /** * After each Blocktank buy, Home ActivityShort inserts a Transfer row at the top. * Do not open Savings to read Activity-*: a delayed Background Payments sheet can * cover ActivitySavings, and dismissing that sheet leaves Home — where the * transfers are already visible as ActivityShort-*. + * + * Use expectTextWithin (all descendants) — same pattern as @transfer_max. + * Android amount minus is a middle TextView; first/last probes miss it. */ async function expectHomeTransferRows(transferCount: 1 | 2) { let transferRowIds: readonly string[]; @@ -234,11 +208,20 @@ async function expectHomeTransferRows(transferCount: 1 | 2) { } } + // ActivityShort-0 is often still the prior Received deposit row; poll until + // Transfer (and amount minus) show via all-descendant expectTextWithin — + // same probe @transfer_max uses. Do not waitForDisplayed alone first. await browser.waitUntil( async () => { await dismissHomeSheetsIfPresent(); for (const rowId of transferRowIds) { - if (!(await activityShortShowsTransfer(rowId))) { + if (!(await elementById(rowId).isDisplayed().catch(() => false))) { + return false; + } + try { + await expectTextWithin(rowId, 'Transfer', { timeout: 1_500 }); + await expectTextWithin(rowId, '-', { timeout: 1_500 }); + } catch { return false; } }