Skip to content

Commit 41f1192

Browse files
committed
test(mobile): add e2e page object and specs for main navigation
Add MainNavigationPage with selectors for both Wallet 4.0 and legacy navigation elements. Add spec files for W40 and legacy navigation flows covering tab switching and destination page verification. Note: W40 specs are blocked by a DetoxSync/BlurView crash on iOS (NSNull __detox_sync_untrackAnimation) and require a fix before running. LIVE-24697 chore(mobile): add changeset for e2e main navigation tests LIVE-24697
1 parent 7106257 commit 41f1192

File tree

13 files changed

+442
-8
lines changed

13 files changed

+442
-8
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"live-mobile": minor
3+
---
4+
5+
Add E2E test infrastructure for main navigation (Wallet 4.0 and legacy)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* eslint-disable @typescript-eslint/no-var-requires */
2+
/**
3+
* Mock for @sbaiahmed1/react-native-blur used during E2E (Detox) builds.
4+
*
5+
* BlurView's UIViewPropertyAnimator sets an NSNull delegate on CAAnimation,
6+
* which crashes DetoxSync's animation tracking on iOS.
7+
* Replacing native blur components with plain Views avoids the crash while
8+
* keeping the rest of the UI intact.
9+
*/
10+
const React = require("react");
11+
const { View } = require("react-native");
12+
13+
const passthrough = React.forwardRef((props, ref) => {
14+
const { style, children } = props;
15+
return React.createElement(View, { ref, style }, children);
16+
});
17+
18+
passthrough.displayName = "BlurViewMock";
19+
20+
module.exports = {
21+
BlurView: passthrough,
22+
VibrancyView: passthrough,
23+
LiquidGlassView: passthrough,
24+
LiquidGlassContainer: passthrough,
25+
ProgressiveBlurView: passthrough,
26+
BlurSwitch: passthrough,
27+
default: passthrough,
28+
};

apps/ledger-live-mobile/rspack.config.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ const withRozeniteUrlFix = rozeniteConfig => {
109109
};
110110
};
111111

112+
const isDetoxBuild = process.env.DETOX === "1" || (process.env.ENVFILE || "").includes("mock");
113+
114+
const detoxAliases = isDetoxBuild
115+
? {
116+
"@sbaiahmed1/react-native-blur": path.resolve(__dirname, "e2e/mocks/react-native-blur.js"),
117+
}
118+
: {};
119+
112120
const hermesNonCompatibleDependencies = ["@polkadot/types-codec"];
113121

114122
/**
@@ -140,6 +148,7 @@ export default withRozeniteUrlFix(
140148
modules: nodeModulesPaths,
141149
alias: {
142150
...buildTsAlias(tsconfig.compilerOptions.paths),
151+
...detoxAliases,
143152
// Packages with malformed exports field (missing "." subpath) - resolve to browser entry
144153
"@aptos-labs/aptos-client": resolvePackageFile(
145154
"@aptos-labs/aptos-client",

apps/ledger-live-mobile/src/components/RootNavigator/MainNavigator/LegacyTabNavigator.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ import EarnLiveAppNavigator from "../EarnLiveAppNavigator";
1212
import { Tab } from "./tabNavigator";
1313
import type { LegacyTabNavigatorProps } from "./types";
1414

15+
const LEGACY_TAB_TEST_IDS: Partial<Record<string, string>> = {
16+
[NavigatorName.Portfolio]: "tab-bar-portfolio",
17+
[NavigatorName.Earn]: "tab-bar-earn",
18+
[NavigatorName.Web3HubTab]: "tab-bar-discover",
19+
[NavigatorName.Discover]: "tab-bar-discover",
20+
[NavigatorName.MyLedger]: "TabBarManager",
21+
};
22+
1523
export function LegacyTabNavigator({
1624
tabBar,
1725
screenOptions,
@@ -31,7 +39,7 @@ export function LegacyTabNavigator({
3139
options={{
3240
headerShown: false,
3341
tabBarIcon: props => <PortfolioTabIcon {...props} />,
34-
tabBarButtonTestID: "tab-bar-portfolio",
42+
tabBarButtonTestID: LEGACY_TAB_TEST_IDS[NavigatorName.Portfolio],
3543
}}
3644
listeners={({ navigation }) => ({
3745
tabPress: e => {
@@ -54,10 +62,11 @@ export function LegacyTabNavigator({
5462
<TabIcon
5563
Icon={IconsLegacy.LendMedium}
5664
i18nKey={earnYieldLabel}
57-
testID="tab-bar-earn"
65+
testID={LEGACY_TAB_TEST_IDS[NavigatorName.Earn]}
5866
{...props}
5967
/>
6068
),
69+
tabBarButtonTestID: LEGACY_TAB_TEST_IDS[NavigatorName.Earn],
6170
}}
6271
listeners={({ navigation }) => ({
6372
tabPress: e => {
@@ -102,7 +111,7 @@ export function LegacyTabNavigator({
102111
tabBarIcon: props => (
103112
<TabIcon Icon={IconsLegacy.PlanetMedium} i18nKey="tabs.discover" {...props} />
104113
),
105-
tabBarButtonTestID: "tab-bar-discover",
114+
tabBarButtonTestID: LEGACY_TAB_TEST_IDS[NavigatorName.Web3HubTab],
106115
}}
107116
listeners={({ navigation }) => ({
108117
tabPress: e => {
@@ -122,7 +131,7 @@ export function LegacyTabNavigator({
122131
tabBarIcon: props => (
123132
<TabIcon Icon={IconsLegacy.PlanetMedium} i18nKey="tabs.discover" {...props} />
124133
),
125-
tabBarButtonTestID: "tab-bar-discover",
134+
tabBarButtonTestID: LEGACY_TAB_TEST_IDS[NavigatorName.Discover],
126135
}}
127136
listeners={({ navigation }) => ({
128137
tabPress: e => {
@@ -142,7 +151,7 @@ export function LegacyTabNavigator({
142151
options={{
143152
headerShown: false,
144153
tabBarIcon: props => <ManagerTabIcon {...props} />,
145-
tabBarButtonTestID: "TabBarManager",
154+
tabBarButtonTestID: LEGACY_TAB_TEST_IDS[NavigatorName.MyLedger],
146155
}}
147156
listeners={({ navigation }) => ({
148157
tabPress: e => {

apps/ledger-live-mobile/src/mvvm/components/MainTabBar/MainTabBarView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const MainTabBarView: React.FC<MainTabBarViewProps> = ({
2222

2323
return (
2424
<Animated.View
25+
testID="w40-tab-bar"
2526
entering={FadeInDown}
2627
exiting={FadeOutDown}
2728
pointerEvents="box-none"
@@ -50,6 +51,7 @@ export const MainTabBarView: React.FC<MainTabBarViewProps> = ({
5051
label={item.label}
5152
icon={item.icon}
5253
activeIcon={item.activeIcon}
54+
testID={item.testID}
5355
/>
5456
))}
5557
</TabBar>

apps/ledger-live-mobile/src/mvvm/components/MainTabBar/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import type { BottomTabBarProps } from "@react-navigation/bottom-tabs";
22
import type { TabBarItemProps } from "@ledgerhq/lumen-ui-rnative";
33

4-
export type TabItemConfig = Pick<TabBarItemProps, "value" | "label" | "icon" | "activeIcon">;
4+
export type TabItemConfig = Pick<
5+
TabBarItemProps,
6+
"value" | "label" | "icon" | "activeIcon" | "testID"
7+
>;
58

69
export interface MainTabBarViewProps {
710
readonly activeRouteName: string;

apps/ledger-live-mobile/src/mvvm/components/MainTabBar/useMainTabBarViewModel.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ const TAB_ICONS: Partial<Record<string, TabIconConfig>> = {
3131
[NavigatorName.Earn]: { icon: Chart5, activeIcon: Chart5Fill },
3232
[NavigatorName.CardTab]: { icon: CreditCard, activeIcon: CreditCardFill },
3333
};
34+
35+
const TAB_TEST_IDS: Partial<Record<string, string>> = {
36+
[NavigatorName.Portfolio]: "w40-tab-home",
37+
[NavigatorName.Swap]: "w40-tab-swap",
38+
[NavigatorName.Earn]: "w40-tab-earn",
39+
[NavigatorName.CardTab]: "w40-tab-card",
40+
};
41+
3442
export const useMainTabBarViewModel = ({
3543
state,
3644
navigation,
@@ -43,6 +51,7 @@ export const useMainTabBarViewModel = ({
4351
state.routes.map(route => ({
4452
value: route.name,
4553
label: t(LABELKEY_MAP[route.name] ?? route.name),
54+
testID: TAB_TEST_IDS[route.name],
4655
...TAB_ICONS[route.name],
4756
})),
4857
[state.routes, t],

apps/ledger-live-mobile/src/screens/MyLedgerChooseDevice/wallet40HeaderOptions.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import React from "react";
22
import HeaderBackButton from "LLM/components/Navigation/HeaderBackButton";
33
import HeaderTitle from "LLM/components/Navigation/HeaderTitle";
44

5+
export const HEADER_BACK_BUTTON_TEST_ID = "header-back-button";
6+
57
export const wallet40HeaderOptions = {
68
headerShown: true,
79
headerTitle: () => <HeaderTitle titleKey="manager.title" />,
8-
headerLeft: () => <HeaderBackButton />,
10+
headerLeft: () => <HeaderBackButton testID={HEADER_BACK_BUTTON_TEST_ID} />,
911
};

apps/ledger-live-mobile/src/screens/PTX/Earn/EarnV2Webview/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const EarnV2Webview = ({
5555
};
5656

5757
return (
58-
<View style={{ flex: 1, overflow: "visible" }}>
58+
<View testID="earn-screen" style={{ flex: 1, overflow: "visible" }}>
5959
{isPtxUiV2 && !hideMainNavigator && <EarnBackground scrollY={scrollY} />}
6060
<View style={{ flex: 1, zIndex: 1 }} pointerEvents="box-none">
6161
{manifest ? (

e2e/mobile/page/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import StakePage from "./trade/stake.page";
2525
import SwapPage from "./trade/swap.page";
2626
import SwapLiveAppPage from "./liveApps/swapLiveApp";
2727
import WalletTabNavigatorPage from "./wallet/walletTabNavigator.page";
28+
import MainNavigationPage from "./wallet/mainNavigation.page";
2829
import CeloManageAssetsPage from "./trade/celoManageAssets.page";
2930
import TransferMenuDrawer from "./wallet/transferMenu.drawer";
3031
import BuySellPage from "./trade/buySell.page";
@@ -76,6 +77,7 @@ export class Application {
7677
private swapLiveAppInstance = lazyInit(SwapLiveAppPage);
7778
private swapPageInstance = lazyInit(SwapPage);
7879
private walletTabNavigatorPageInstance = lazyInit(WalletTabNavigatorPage);
80+
private mainNavigationPageInstance = lazyInit(MainNavigationPage);
7981
private celoManageAssetsPageInstance = lazyInit(CeloManageAssetsPage);
8082
private TransferMenuDrawerInstance = lazyInit(TransferMenuDrawer);
8183
private buySellPageInstance = lazyInit(BuySellPage);
@@ -195,6 +197,10 @@ export class Application {
195197
return this.walletTabNavigatorPageInstance();
196198
}
197199

200+
public get mainNavigation() {
201+
return this.mainNavigationPageInstance();
202+
}
203+
198204
public get celoManageAssets() {
199205
return this.celoManageAssetsPageInstance();
200206
}

0 commit comments

Comments
 (0)