From 37dca0bd6acaf9f1af13fe03cd204cff9c4ee453 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 18 Sep 2026 15:41:08 +0000 Subject: [PATCH 1/2] fix: best-effort dismiss of Background Payments intro sheet Soften dismissBackgroundPaymentsTimedSheet to a waitForToastBestEffort-style poll so ephemeral intro sheets that already auto-dismissed do not fail send coverage. Matches the same harness change as #234. Co-authored-by: piotr-iohk --- test/helpers/actions.ts | 78 ++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 17 deletions(-) diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index d42f8ad..09a7685 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -1471,28 +1471,72 @@ export async function acknowledgeExternalSuccess() { await sleep(300); } +/** + * Dismisses the Background Payments timed sheet if it is present. + * + * Best-effort: the intro can already be gone (auto-dismissed, previously + * acknowledged, or never queued after HeaderMenu). Does not throw when the + * dismiss control is missing — callers continue. When the intro IS shown, + * it is still dismissed via Later / Cancel so remaining coverage can run + * on an unobstructed home screen. + * + * Pattern matches waitForToastBestEffort: poll for visibility, act if seen, + * swallow timeout if the sheet never appeared or vanished mid-dismiss. + * + * @returns true if the sheet was observed and a dismiss was attempted, + * false if it was already gone. + */ export async function dismissBackgroundPaymentsTimedSheet({ triggerTimedSheet = false, -}: { triggerTimedSheet?: boolean } = {}) { - if (triggerTimedSheet) { - await doTriggerTimedSheet(); + timeout = 10_000, +}: { triggerTimedSheet?: boolean; timeout?: number } = {}): Promise { + const sheetId = driver.isAndroid + ? 'BackgroundPaymentsIntro-later' + : 'BackgroundPaymentsDescription'; + const dismissId = driver.isAndroid ? 'BackgroundPaymentsIntro-later' : 'BackgroundPaymentsCancel'; + + await triggerTimedSheetUnlessPresent(sheetId, triggerTimedSheet); + + const el = elementById(sheetId); + let sheetSeen = false; + + try { + await browser.waitUntil( + async () => { + const displayed = await el.isDisplayed().catch(() => false); + if (displayed) { + sheetSeen = true; + return true; + } + return false; + }, + { timeout, interval: 200 } + ); + } catch { + // Sheet wasn't displayed within timeout — may have already appeared and + // dismissed, or never appeared after the HeaderMenu trigger race. + console.info(`→ ${sheetId} not displayed; treating Background Payments intro as already gone`); + return false; } - if (driver.isAndroid) { - await elementById('BackgroundPaymentsIntro-later').waitForDisplayed(); - await sleep(500); // wait for the app to settle - await tap('BackgroundPaymentsIntro-later'); - } else { - const description = elementById('BackgroundPaymentsDescription'); - await description.waitForDisplayed(); - const cancel = elementById('BackgroundPaymentsCancel'); - await cancel.waitForDisplayed(); - // The iOS sheet animates while appearing. Click a fresh element reference - // immediately; the generic tap() delay makes this button prone to staleness. - await cancel.click(); - await description.waitForDisplayed({ reverse: true }); + if (!sheetSeen) { + return false; } - await sleep(500); + + await sleep(500); // wait for the app to settle + try { + const dismissEl = elementById(dismissId); + const stillShown = await dismissEl.isDisplayed().catch(() => false); + if (!stillShown) { + console.info(`→ ${dismissId} gone before dismiss tap; treating intro as already dismissed`); + return true; + } + await dismissEl.click(); + await sleep(500); + } catch (error) { + console.info(`→ ${dismissId} disappeared before dismiss tap; continuing`, error); + } + return true; } async function isTestIdDisplayed(testId: string): Promise { From 1eae036500f99ac472d01557906a74c3764d4d23 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 21 Sep 2026 05:23:37 +0000 Subject: [PATCH 2/2] test: match boost_2 RBF activity list to three-row UI After a successful RBF boost the home list keeps the pending replacement (Sent / Confirms in) and the original send (Removed from Mempool) plus the receive. Stop requiring BoostingIcon / BoostedButton / StatusBoosting on that path. Co-authored-by: piotr-iohk --- test/specs/boost.e2e.ts | 43 +++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/test/specs/boost.e2e.ts b/test/specs/boost.e2e.ts index e7abf5a..4cdabd2 100644 --- a/test/specs/boost.e2e.ts +++ b/test/specs/boost.e2e.ts @@ -177,19 +177,12 @@ describe('@boost - Boost', () => { await dragOnElement('GRAB', 'right', 0.95); // Swipe to confirm await waitForToast('BoostSuccessToast'); - // check Activity - await elementById('BoostingIcon').waitForDisplayed(); - await elementById('ActivityShort-0').waitForDisplayed(); - await elementById('ActivityShort-1').waitForDisplayed(); - await expect(elementById('ActivityShort-2')).not.toBeDisplayed(); - await expectTextWithin('ActivityShort-0', '-'); - await expectTextWithin('ActivityShort-1', '100 000'); - await expectTextWithin('ActivityShort-1', '+'); + // RBF keeps the pending replacement and the original removed-from-mempool send + await expectRbfHomeActivityAfterBoost(); - // new tx + // pending replacement await tap('ActivityShort-0'); - await elementById('BoostedButton').waitForDisplayed(); - await elementById('StatusBoosting').waitForDisplayed(); + await expectText('Confirming'); await expectTextWithin('ActivityAmount', '10 000'); const newFee = await (await elementByIdWithin('ActivityFee', 'MoneyText')).getText(); console.info({ newFee }); @@ -198,7 +191,12 @@ describe('@boost - Boost', () => { console.info({ newTxId }); await expect(Number(oldFee.replace(' ', '')) < Number(newFee.replace(' ', ''))).toBe(true); await expect(oldTxId !== newTxId).toBe(true); - await elementById('RBFBoosted').waitForDisplayed(); + await doNavigationClose(); + + // original replaced tx + await tap('ActivityShort-1'); + await elementById('StatusRemoved').waitForDisplayed(); + await expectText('Removed from Mempool'); await doNavigationClose(); // wipe & restore @@ -207,10 +205,10 @@ describe('@boost - Boost', () => { await restoreWallet(seed); // check activity after restore - (await elementByIdWithin('ActivityShort-0', 'BoostingIcon')).waitForDisplayed(); + await expectRbfHomeActivityAfterBoost(); await tap('ActivityShort-0'); - await elementById('BoostedButton').waitForDisplayed(); - await elementById('StatusBoosting').waitForDisplayed(); + await expectText('Confirming'); + await expectTextWithin('ActivityAmount', '10 000'); await doNavigationClose(); // mine new block @@ -225,3 +223,18 @@ describe('@boost - Boost', () => { await doNavigationClose(); }); }); + +async function expectRbfHomeActivityAfterBoost() { + await elementById('ActivityShort-0').waitForDisplayed(); + await elementById('ActivityShort-1').waitForDisplayed(); + await elementById('ActivityShort-2').waitForDisplayed(); + await expectTextWithin('ActivityShort-0', 'Sent'); + await expectTextWithin('ActivityShort-0', 'Confirms in'); + await expectTextWithin('ActivityShort-0', '-'); + await expectTextWithin('ActivityShort-1', 'Sent'); + await expectTextWithin('ActivityShort-1', 'Removed from Mempool'); + await expectTextWithin('ActivityShort-1', '-'); + await expectTextWithin('ActivityShort-2', 'Received'); + await expectTextWithin('ActivityShort-2', '100 000'); + await expectTextWithin('ActivityShort-2', '+'); +}