From ceed7cec93f5c1ae44ec6883e00912c523c64c29 Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Tue, 25 Feb 2025 18:14:41 +0100 Subject: [PATCH] test: fix flaky tests --- e2e/backup.e2e.js | 6 ++-- e2e/boost.e2e.js | 8 +++-- e2e/helpers.js | 35 +++++++++++-------- e2e/lightning.e2e.js | 12 ++++--- e2e/lnurl.e2e.js | 4 +-- e2e/onchain.e2e.js | 4 +-- e2e/send.e2e.js | 4 +-- e2e/slashtags.e2e.js | 2 ++ e2e/transfer.e2e.js | 19 +++++----- e2e/widgets.e2e.js | 4 +-- src/hooks/useBlocksWidget.ts | 6 ++++ src/hooks/useNewsWidget.ts | 6 ++++ src/hooks/usePriceWidget.ts | 6 ++++ src/hooks/useWeatherWidget.ts | 6 ++++ src/screens/Settings/BackupSettings/index.tsx | 13 ++++++- 15 files changed, 91 insertions(+), 44 deletions(-) diff --git a/e2e/backup.e2e.js b/e2e/backup.e2e.js index bcd989ca2..3e518f0dd 100644 --- a/e2e/backup.e2e.js +++ b/e2e/backup.e2e.js @@ -11,6 +11,7 @@ import { electrumPort, getSeed, restoreWallet, + waitForBackup, } from './helpers'; import initWaitForElectrumToSync from '../__tests__/utils/wait-for-electrum'; @@ -95,7 +96,7 @@ d('Backup', () => { await element(by.id('NavigationClose')).atIndex(0).tap(); // remove 2 default widgets, leave PriceWidget - await element(by.id('WalletsScrollView')).scroll(200, 'down', NaN, 0.85); + await element(by.id('WalletsScrollView')).scroll(200, 'down', 0); await element(by.id('WidgetsEdit')).tap(); for (const w of ['NewsWidget', 'BlocksWidget']) { await element(by.id('WidgetActionDelete').withAncestor(by.id(w))).tap(); @@ -107,6 +108,7 @@ d('Backup', () => { // restore wallet const seed = await getSeed(); + await waitForBackup(); await restoreWallet(seed); // check settings @@ -124,7 +126,7 @@ d('Backup', () => { await sleep(200); // animation // check widgets - await element(by.id('WalletsScrollView')).scroll(300, 'down', NaN, 0.85); + await element(by.id('WalletsScrollView')).scroll(300, 'down', 0); await expect(element(by.id('PriceWidget'))).toExist(); await expect(element(by.id('NewsWidget'))).not.toExist(); await expect(element(by.id('BlocksWidget'))).not.toExist(); diff --git a/e2e/boost.e2e.js b/e2e/boost.e2e.js index 2b0f01787..dc9956250 100644 --- a/e2e/boost.e2e.js +++ b/e2e/boost.e2e.js @@ -13,6 +13,7 @@ import { electrumPort, getSeed, restoreWallet, + waitForBackup, } from './helpers'; import initWaitForElectrumToSync from '../__tests__/utils/wait-for-electrum'; @@ -100,7 +101,7 @@ d('Boost', () => { await element(by.id('Close')).tap(); // check Activity - await element(by.id('WalletsScrollView')).scrollTo('bottom', NaN, 0.85); + await element(by.id('WalletsScrollView')).scrollTo('bottom', 0); await expect(element(by.id('ActivityShort-1'))).toBeVisible(); await expect( element(by.text('100 000').withAncestor(by.id('ActivityShort-2'))), @@ -211,7 +212,7 @@ d('Boost', () => { await element(by.id('Close')).tap(); // check Activity - await element(by.id('WalletsScrollView')).scrollTo('bottom', NaN, 0.85); + await element(by.id('WalletsScrollView')).scrollTo('bottom', 0); await expect(element(by.id('ActivityShort-1'))).toBeVisible(); await expect( element(by.text('100 000').withAncestor(by.id('ActivityShort-2'))), @@ -273,10 +274,11 @@ d('Boost', () => { // wipe & restore const seed = await getSeed(); + await waitForBackup(); await restoreWallet(seed); // check activity after restore - await element(by.id('WalletsScrollView')).scrollTo('bottom', NaN, 0.85); + await element(by.id('WalletsScrollView')).scrollTo('bottom', 0); await expect(element(by.id('BoostingIcon'))).toBeVisible(); await element(by.id('ActivityShort-1')).tap(); await expect(element(by.id('BoostedButton'))).toBeVisible(); diff --git a/e2e/helpers.js b/e2e/helpers.js index 1e2f7a37e..498cf1681 100644 --- a/e2e/helpers.js +++ b/e2e/helpers.js @@ -23,7 +23,7 @@ export const checkComplete = (name) => { } for (const n of name) { - if (!fs.existsSync(path.join(LOCK_PATH, 'lock-' + n))) { + if (!fs.existsSync(path.join(LOCK_PATH, `lock-${n}`))) { return false; } } @@ -38,7 +38,7 @@ export const markComplete = (name) => { } fs.mkdirSync(LOCK_PATH, { recursive: true }); - fs.writeFileSync(path.join(LOCK_PATH, 'lock-' + name), '1'); + fs.writeFileSync(path.join(LOCK_PATH, `lock-${name}`), '1'); }; export const sleep = (ms) => { @@ -63,15 +63,13 @@ export async function waitForElementAttribute( if (attributes[attribute] === expectedValue) { console.log(`${elementId} has attribute ${attribute}=${expectedValue}`); break; - } else { - console.log( - `Waiting for ${elementId} to have attribute ${attribute}=${expectedValue}...`, - ); - await new Promise((resolve) => { - setTimeout(resolve, 1000); - }); - timeout--; } + + console.log( + `Waiting for ${elementId} to have attribute ${attribute}=${expectedValue}...`, + ); + await sleep(1000); + timeout--; } } @@ -102,7 +100,7 @@ export const completeOnboarding = async () => { await element(by.id('WalletOnboardingClose')).tap(); await sleep(3000); // wait for redux-persist to save state return; - } catch (e) {} + } catch (_e) {} } throw new Error('Tapping "WalletOnboardingClose" timeout'); @@ -124,9 +122,7 @@ export const launchAndWait = async () => { await element(by.id('SuggestionsLabel')).tap(); await sleep(1000); break; - } catch (e) { - continue; - } + } catch (_e) {} } }; @@ -246,6 +242,15 @@ export const restoreWallet = async (seed, passphrase) => { try { await element(by.id('SuggestionsLabel')).tap(); break; - } catch (e) {} + } catch (_e) {} } }; + +export const waitForBackup = async () => { + await element(by.id('Settings')).tap(); + await element(by.id('BackupSettings')).tap(); + await waitFor(element(by.id('AllSynced'))) + .toBeVisible() + .withTimeout(40000); + await element(by.id('NavigationClose')).atIndex(0).tap(); +}; diff --git a/e2e/lightning.e2e.js b/e2e/lightning.e2e.js index 62dfc1496..1943ede0a 100644 --- a/e2e/lightning.e2e.js +++ b/e2e/lightning.e2e.js @@ -17,6 +17,7 @@ import { waitForPeerConnection, restoreWallet, getSeed, + waitForBackup, } from './helpers'; d = checkComplete('lighting-1') ? describe.skip : describe; @@ -83,7 +84,7 @@ d('Lightning', () => { await waitFor(element(by.id('LDKNodeID'))) .toBeVisible() .withTimeout(60000); - let { label: ldkNodeId } = await element( + const { label: ldkNodeId } = await element( by.id('LDKNodeID'), ).getAttributes(); await element(by.id('NavigationBack')).atIndex(0).tap(); @@ -123,7 +124,7 @@ d('Lightning', () => { await expect( element(by.id('MoneyText').withAncestor(by.id('TotalSize'))), ).toHaveText('100 000'); - await element(by.id('ChannelScrollView')).scrollTo('bottom', NaN, 0.1); + await element(by.id('ChannelScrollView')).scrollTo('bottom', Number.NaN, 0.1); await expect(element(by.id('IsUsableYes'))).toBeVisible(); await element(by.id('NavigationClose')).atIndex(0).tap(); @@ -234,7 +235,7 @@ d('Lightning', () => { .withTimeout(10000); // check tx history - await element(by.id('WalletsScrollView')).scroll(1000, 'down', NaN, 0.85); + await element(by.id('WalletsScrollView')).scroll(1000, 'down', 0); await expect( element(by.text('1 000').withAncestor(by.id('ActivityShort-1'))), ).toBeVisible(); @@ -315,6 +316,7 @@ d('Lightning', () => { // wipe and restore wallet const seed = await getSeed(); + await waitForBackup(); await restoreWallet(seed); // check balance @@ -325,7 +327,7 @@ d('Lightning', () => { .withTimeout(10000); // check tx history - await element(by.id('WalletsScrollView')).scroll(1000, 'down', NaN, 0.85); + await element(by.id('WalletsScrollView')).scroll(1000, 'down', 0); await expect( element(by.text('111').withAncestor(by.id('ActivityShort-2'))), ).toBeVisible(); @@ -339,7 +341,7 @@ d('Lightning', () => { await sleep(100); await element(by.id('Channels')).tap(); await element(by.id('Channel')).atIndex(0).tap(); - await element(by.id('ChannelScrollView')).scrollTo('bottom', NaN, 0.1); + await element(by.id('ChannelScrollView')).scrollTo('bottom', Number.NaN, 0.1); await expect(element(by.id('IsUsableYes'))).toBeVisible(); // close channel diff --git a/e2e/lnurl.e2e.js b/e2e/lnurl.e2e.js index ae2680664..396038fc4 100644 --- a/e2e/lnurl.e2e.js +++ b/e2e/lnurl.e2e.js @@ -104,7 +104,7 @@ d('LNURL', () => { await waitFor(element(by.id('LDKNodeID'))) .toBeVisible() .withTimeout(60000); - let { label: ldkNodeID } = await element( + const { label: ldkNodeID } = await element( by.id('LDKNodeID'), ).getAttributes(); await element(by.id('NavigationClose')).atIndex(0).tap(); @@ -200,7 +200,7 @@ d('LNURL', () => { .withTimeout(10000); await element(by.id('Close')).tap(); // check if comment is displayed - await element(by.id('WalletsScrollView')).scrollTo('bottom', NaN, 0.85); + await element(by.id('WalletsScrollView')).scrollTo('bottom', 0); await element(by.id('ActivityShort-1')).tap(); await expect(element(by.id('InvoiceComment'))).toHaveText('test comment'); await element(by.id('NavigationClose')).tap(); diff --git a/e2e/onchain.e2e.js b/e2e/onchain.e2e.js index 1056048c7..869e911e5 100644 --- a/e2e/onchain.e2e.js +++ b/e2e/onchain.e2e.js @@ -145,7 +145,7 @@ d('Onchain', () => { ).toHaveText('0'); // check Activity - await element(by.id('WalletsScrollView')).scroll(1000, 'down', NaN, 0.85); + await element(by.id('WalletsScrollView')).scroll(1000, 'down', 0); await expect(element(by.id('ActivityShort-1'))).toBeVisible(); await expect(element(by.id('ActivityShort-2'))).toBeVisible(); await expect(element(by.id('ActivityShort-3'))).toBeVisible(); @@ -311,7 +311,7 @@ d('Onchain', () => { ).toHaveText('0'); // check number of outputs for send tx - await element(by.id('WalletsScrollView')).scroll(1000, 'down', NaN, 0.85); + await element(by.id('WalletsScrollView')).scroll(1000, 'down', 0); await expect(element(by.id('ActivityShort-1'))).toBeVisible(); await expect(element(by.id('ActivityShort-2'))).toBeVisible(); await element(by.id('ActivityShowAll')).tap(); diff --git a/e2e/send.e2e.js b/e2e/send.e2e.js index e77bfefef..add141985 100644 --- a/e2e/send.e2e.js +++ b/e2e/send.e2e.js @@ -198,7 +198,7 @@ d('Send', () => { await waitFor(element(by.id('LDKNodeID'))) .toBeVisible() .withTimeout(60000); - let { label: ldkNodeId } = await element( + const { label: ldkNodeId } = await element( by.id('LDKNodeID'), ).getAttributes(); await element(by.id('NavigationBack')).atIndex(0).tap(); @@ -238,7 +238,7 @@ d('Send', () => { await expect( element(by.id('MoneyText').withAncestor(by.id('TotalSize'))), ).toHaveText('100 000'); - await element(by.id('ChannelScrollView')).scrollTo('bottom', NaN, 0.1); + await element(by.id('ChannelScrollView')).scrollTo('bottom', Number.NaN, 0.1); await expect(element(by.id('IsUsableYes'))).toBeVisible(); await element(by.id('NavigationClose')).atIndex(0).tap(); await sleep(500); diff --git a/e2e/slashtags.e2e.js b/e2e/slashtags.e2e.js index 515e5fdd2..45f653f9c 100644 --- a/e2e/slashtags.e2e.js +++ b/e2e/slashtags.e2e.js @@ -11,6 +11,7 @@ import { electrumPort, getSeed, restoreWallet, + waitForBackup, } from './helpers'; import initWaitForElectrumToSync from '../__tests__/utils/wait-for-electrum'; @@ -227,6 +228,7 @@ d('Profile and Contacts', () => { // WIPE APP AND RESTORE FROM THE SEED const seed = await getSeed(); + await waitForBackup(); await restoreWallet(seed); // CHECK PROFILE, CONTACTS, TRANSACTION diff --git a/e2e/transfer.e2e.js b/e2e/transfer.e2e.js index b9a525e78..f0eeb5534 100644 --- a/e2e/transfer.e2e.js +++ b/e2e/transfer.e2e.js @@ -17,6 +17,7 @@ import { waitForPeerConnection, getSeed, restoreWallet, + waitForBackup, } from './helpers'; d = checkComplete(['transfer-1', 'transfer-2']) ? describe.skip : describe; @@ -104,7 +105,7 @@ d('Transfer', () => { await expect(element(by.text('100 000'))).toBeVisible(); await element(by.id('SpendingAdvancedDefault')).tap(); await element(by.id('SpendingAdvancedNumberField')).tap(); - let { label } = await element( + const { label } = await element( by.id('SpendingAdvancedNumberField'), ).getAttributes(); const lspBalance = Number.parseInt(label, 10); @@ -211,13 +212,14 @@ d('Transfer', () => { await element(by.id('NavigationClose')).tap(); const seed = await getSeed(); + await waitForBackup(); await restoreWallet(seed); // check transfer card await expect(element(by.id('Suggestion-lightningSettingUp'))).toBeVisible(); // check activity after restore - await element(by.id('WalletsScrollView')).scrollTo('bottom', NaN, 0.85); + await element(by.id('WalletsScrollView')).scrollTo('bottom', 0); await element(by.id('ActivityShort-1')).tap(); await expect(element(by.id('StatusTransfer'))).toBeVisible(); @@ -234,10 +236,11 @@ d('Transfer', () => { .withTimeout(30000); // reset & restore again + await waitForBackup(); await restoreWallet(seed); // check activity after restore - await element(by.id('WalletsScrollView')).scrollTo('bottom', NaN, 0.85); + await element(by.id('WalletsScrollView')).scrollTo('bottom', 0); await expect(element(by.id('BoostingIcon'))).toBeVisible(); await element(by.id('ActivityShort-1')).tap(); await expect(element(by.id('StatusBoosting'))).toBeVisible(); @@ -275,9 +278,7 @@ d('Transfer', () => { await waitFor(element(by.id('LDKNodeID'))) .toBeVisible() .withTimeout(60000); - let { label: ldkNodeId } = await element( - by.id('LDKNodeID'), - ).getAttributes(); + const { label: ldkNodeId } = await element(by.id('LDKNodeID')).getAttributes(); await element(by.id('NavigationClose')).tap(); // Get LND node id @@ -331,12 +332,10 @@ d('Transfer', () => { await expect(element(by.id('Suggestion-lightningSettingUp'))).toBeVisible(); // check activity - await element(by.id('WalletsScrollView')).scrollTo('bottom', NaN, 0.85); + await element(by.id('WalletsScrollView')).scrollTo('bottom', 0); await expect(element(by.text('From Savings (±30m)'))).toBeVisible(); await element(by.id('ActivityShort-1')).tap(); await expect(element(by.text('Transfer (±30m)'))).toBeVisible(); - await element(by.id('NavigationClose')).tap(); - await element(by.id('WalletsScrollView')).scrollTo('top', NaN, 0.85); // Mine 3 blocks await rpc.generateToAddress(3, await rpc.getNewAddress()); @@ -361,7 +360,7 @@ d('Transfer', () => { await expect( element(by.id('MoneyText').withAncestor(by.id('TotalSize'))), ).toHaveText('20 000'); - await element(by.id('ChannelScrollView')).scrollTo('bottom', NaN, 0.1); + await element(by.id('ChannelScrollView')).scrollTo('bottom', Number.NaN, 0.1); await expect(element(by.id('IsUsableYes'))).toBeVisible(); await element(by.id('NavigationClose')).atIndex(0).tap(); diff --git a/e2e/widgets.e2e.js b/e2e/widgets.e2e.js index 7cb52c7c2..a042e13fa 100644 --- a/e2e/widgets.e2e.js +++ b/e2e/widgets.e2e.js @@ -35,7 +35,7 @@ d('Widgets', () => { } // add price widget - await element(by.id('WalletsScrollView')).scroll(300, 'down', NaN, 0.85); + await element(by.id('WalletsScrollView')).scroll(300, 'down', 0); await element(by.id('WidgetsAdd')).tap(); await element(by.id('WidgetsOnboarding-button')).tap(); await element(by.id('WidgetListItem-price')).tap(); @@ -50,7 +50,7 @@ d('Widgets', () => { await element(by.id('WidgetEditField-showSource')).tap(); await element(by.id('WidgetEditPreview')).tap(); await element(by.id('WidgetSave')).tap(); - await element(by.id('WalletsScrollView')).scroll(200, 'down', NaN, 0.85); + await element(by.id('WalletsScrollView')).scroll(200, 'down', 0); await expect(element(by.id('PriceWidget'))).toBeVisible(); await expect(element(by.id('PriceWidgetRow-BTC/EUR'))).toBeVisible(); await expect(element(by.id('PriceWidgetSource'))).toBeVisible(); diff --git a/src/hooks/useBlocksWidget.ts b/src/hooks/useBlocksWidget.ts index b0c381eaf..dc9b17730 100644 --- a/src/hooks/useBlocksWidget.ts +++ b/src/hooks/useBlocksWidget.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import { __E2E__ } from '../constants/env'; import { i18nTime } from '../utils/i18n'; type TBlocksWidgetData = { @@ -132,6 +133,11 @@ const useBlocksWidget = (): TWidgetState => { fetchData(); + // Don't start polling in E2E tests + if (__E2E__) { + return; + } + const interval = setInterval(fetchData, REFRESH_INTERVAL); return () => { diff --git a/src/hooks/useNewsWidget.ts b/src/hooks/useNewsWidget.ts index e1fbae1e7..6f053b352 100644 --- a/src/hooks/useNewsWidget.ts +++ b/src/hooks/useNewsWidget.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import { __E2E__ } from '../constants/env'; import { timeAgo } from '../utils/helpers'; type TArticle = { @@ -95,6 +96,11 @@ const useNewsWidget = (): TWidgetState => { fetchData(); + // Don't start polling in E2E tests + if (__E2E__) { + return; + } + const interval = setInterval(fetchData, REFRESH_INTERVAL); return () => { diff --git a/src/hooks/usePriceWidget.ts b/src/hooks/usePriceWidget.ts index b4bb1f6bf..742ac9133 100644 --- a/src/hooks/usePriceWidget.ts +++ b/src/hooks/usePriceWidget.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import { __E2E__ } from '../constants/env'; import { tradingPairs } from '../constants/widgets'; import { TGraphPeriod } from '../store/types/widgets'; import { IThemeColors } from '../styles/themes'; @@ -155,6 +156,11 @@ const usePriceWidget = ( const data = await Promise.all(promises); setState({ status: EWidgetStatus.Ready, data }); + // Don't start polling in E2E tests + if (__E2E__) { + return; + } + // Start polling for updates intervalId = setInterval(async () => { try { diff --git a/src/hooks/useWeatherWidget.ts b/src/hooks/useWeatherWidget.ts index d27b1a81b..699b06616 100644 --- a/src/hooks/useWeatherWidget.ts +++ b/src/hooks/useWeatherWidget.ts @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import { __E2E__ } from '../constants/env'; import { refreshOnchainFeeEstimates } from '../store/utils/fees'; import { getDisplayValues, getFiatDisplayValues } from '../utils/displayValues'; @@ -145,6 +146,11 @@ const useWeatherWidget = (): TWidgetState => { fetchData(); + // Don't start polling in E2E tests + if (__E2E__) { + return; + } + const interval = setInterval(fetchData, REFRESH_INTERVAL); return () => { diff --git a/src/screens/Settings/BackupSettings/index.tsx b/src/screens/Settings/BackupSettings/index.tsx index 1132c5e67..16ea9b9dc 100644 --- a/src/screens/Settings/BackupSettings/index.tsx +++ b/src/screens/Settings/BackupSettings/index.tsx @@ -4,6 +4,7 @@ import { StyleSheet, View } from 'react-native'; import { EItemType, IListData } from '../../../components/List'; import Button from '../../../components/buttons/Button'; +import { __E2E__ } from '../../../constants/env'; import { useAppDispatch, useAppSelector } from '../../../hooks/redux'; import { SettingsScreenProps } from '../../../navigation/types'; import { backupSelector } from '../../../store/reselect/backup'; @@ -151,7 +152,7 @@ const BackupSettings = ({ }, channels[0][1]); }, [lightningBackup]); - const categories: Array = [ + const categories: TBackupCategory[] = [ { Icon: NoteIcon, title: t('backup.category_connection_receipts'), @@ -215,6 +216,10 @@ const BackupSettings = ({ }); } + const allSynced = categories.every( + (c) => c.status.synced >= c.status.required, + ); + const settingsListData: IListData[] = useMemo( () => [ { @@ -269,7 +274,13 @@ const BackupSettings = ({ listData={settingsListData} fullHeight={false} /> + + {__E2E__ && allSynced && ( + + All Synced + + )} {t('backup.latest')}