diff --git a/test/helpers/actions.ts b/test/helpers/actions.ts index d42f8ad..a352e64 100644 --- a/test/helpers/actions.ts +++ b/test/helpers/actions.ts @@ -1396,6 +1396,10 @@ export async function waitForToast( * or dismissed too quickly). Does not throw on timeout—callers must handle * verification via other means (e.g., UI state confirmation). * + * Once the toast is observed it is cleared before returning, so it cannot keep + * covering the header: a toast overlays HeaderMenu for its whole lifetime and + * swallows the tap that opens the drawer. + * * On iOS, uses waitToDisappear pattern since toasts render in a separate window * where drag-dismiss hits wrong coordinates. */ @@ -1421,17 +1425,31 @@ export async function waitForToastBestEffort( }, { timeout, interval: pollingInterval } ); - - if (driver.isIOS) { - await el.waitForDisplayed({ reverse: true, timeout: 5_000 }).catch(() => { - // Toast may have dismissed immediately; that's fine - }); - } } catch { // Toast wasn't displayed within timeout—may have already appeared and dismissed // or never appeared. Caller should verify via other means. } + if (!toastSeen) { + return false; + } + + // Re-check instead of trusting toastSeen: dragOnElement waits on the element + // with the global 30s timeout, which would be spent in full on a toast that + // already went away. + if (driver.isAndroid && (await el.isDisplayed().catch(() => false))) { + // Drag it away like waitForToast does: an undismissed toast keeps covering + // HeaderMenu for its full duration, so the next openSettings() tap lands on + // the toast instead of the drawer button. + await dragOnElement(toastId, 'up', 0.2).catch(() => { + // Toast may have auto-dismissed mid-drag; the wait below settles it + }); + } + + await el.waitForDisplayed({ reverse: true, timeout: 5_000 }).catch(() => { + // Toast may have dismissed immediately; that's fine + }); + return toastSeen; } @@ -1444,6 +1462,23 @@ export async function acknowledgeReceivedPayment({ timeout = 30_000 }: { timeout await sleep(300); } +/** Dismisses the received payment sheet when it shows, and does nothing when it does not. + * + * Use this where the sheet is platform- or build-dependent, such as a deposit the wallet first sees + * already confirmed (synonymdev/bitkit-android#797), which Android notifies and iOS does not yet. + */ +export async function dismissReceivedPaymentIfShown({ + timeout = 20_000, +}: { timeout?: number } = {}): Promise { + try { + await acknowledgeReceivedPayment({ timeout }); + return true; + } catch { + console.info('→ No received payment sheet shown, continuing.'); + return false; + } +} + export async function acknowledgeReceivedPaymentIfPresent(): Promise { if (getBackend() === 'local') { await acknowledgeReceivedPayment(); @@ -1471,28 +1506,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 { diff --git a/test/specs/boost.e2e.ts b/test/specs/boost.e2e.ts index e7abf5a..e2c9c97 100644 --- a/test/specs/boost.e2e.ts +++ b/test/specs/boost.e2e.ts @@ -15,6 +15,7 @@ import { restoreWallet, enterAddress, waitForToast, + dismissReceivedPaymentIfShown, } from '../helpers/actions'; import initElectrum from '../helpers/electrum'; import { reinstallApp } from '../helpers/setup'; @@ -111,6 +112,11 @@ describe('@boost - Boost', () => { // mine new block await mineBlocks(1); + + // the restored wallet never saw the boosted tx in the mempool, so this confirmation is its + // first sight of the deposit and the received sheet opens over the screen + await dismissReceivedPaymentIfShown(); + await doNavigationClose(); await sleep(500);