-
Notifications
You must be signed in to change notification settings - Fork 483
E2E:Mobile
Ledger Live Mobile E2E tests use Detox and Jest for React Native end-to-end testing. Shared setup, Speculos behavior, reporting, best practices, and troubleshooting live in the shared E2E pages.
Before running Mobile tests, complete E2E prerequisites & environment.
Cursor users can run /e2e-mobile-onboard for an interactive setup wizard. It checks prerequisites, validates environment variables, and guides fixes step by step.
Mobile E2E tests cover the Ledger Live Mobile framework for iOS and Android. The framework is built for the Ledger Live monorepo and uses:
- Detox: core mobile E2E framework.
- Jest: test runner.
- TypeScript: test implementation language.
- Allure: interactive test reports.
- Speculos: Ledger hardware wallet simulation.
- pnpm + Turborepo: monorepo dependency and build orchestration.
- macOS: required for iOS development and testing.
- Homebrew: install from brew.sh.
-
Xcode: version 16.2 or higher. Install from the Mac App Store and run
xcode-select --install. - Android Studio: latest stable release. Install from developer.android.com/studio. Use the setup wizard to install the SDK and create an Android Virtual Device.
- Android AVD: Pixel 9 Pro with API 36 is recommended.
- Docker Desktop: used for containers, especially Speculos.
CocoaPods requires Ruby:
brew install ruby@3.3
if [ -d "/opt/homebrew/opt/ruby@3.3/bin" ]; then
export PATH=/opt/homebrew/opt/ruby@3.3/bin:$PATH
export PATH=`gem environment gemdir`/bin:$PATH
fi
source ~/.zshrc
gem install bundler:2.5.7
gem install cocoapodsFor a full clean:
rm -rf ios/build
rm -rf node_modules
pnpm clean
pnpm store pruneInstall dependencies needed for Mobile E2E:
pnpm i --filter="live-mobile..." --filter="ledger-live" --filter="live-cli..." --filter="ledger-live-mobile-e2e-tests"
pnpm build:llm:deps
pnpm build:cliThere is also a shorter filtered install used in some flows:
pnpm i --filter="live-mobile..." --filter="ledger-live"Create an Android Emulator named Android_Emulator:
- Open Android Studio.
- Go to Tools -> AVD Manager.
- Click Create Virtual Device.
- Choose a device, for example Pixel 9 Pro.
- Select a system image. API 36 is recommended in the current setup notes.
- Name it
Android_Emulator. - Click Finish.
Also check the Detox Android environment setup guide. The main requirements are:
- Java 11.
- Android SDK Build Tools, SDK Platform Tools, SDK Command Line Tools, Android Emulator, CMake 3.10.2, and NDK 21.4.7075529.
- Shell profile variables similar to:
export JAVA_HOME=`/usr/libexec/java_home`
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator:$ANDROID_HOME/tools/bin/sdkmanager:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/binThere is some inconsistency in React Native, Detox, and Android documentation about ANDROID_ROOT versus ANDROID_SDK_ROOT. ANDROID_SDK_ROOT is deprecated, but both can still work depending on tooling.
Create an iOS Simulator named iOS Simulator:
- Open Simulator from Xcode -> Open Developer Tool -> Simulator, or run
open -a Simulator. - Go to Device -> Manage Devices and Simulators.
- Click +.
- Enter
iOS Simulatoras the name. - Select a recent iPhone, for example iPhone 15.
- Choose iOS 17.0 or later.
- Click Create.
After following the React Native and Detox setup docs, make sure:
- Xcode and Xcode command line tools work with
xcode-select -vandxcrun --version. - Ruby points to your managed Ruby, not
/usr/bin/ruby. -
applesimutilsis installed: AppleSimulatorUtils.
This script prepares local E2E builds using the current recommended configurations:
pnpm clean
pnpm i --filter="live-mobile..." --filter="ledger-live" --filter="live-cli..." --filter="ledger-live-mobile-e2e-tests"
pnpm build:llm:deps
pnpm build:cli
pnpm mobile e2e:build -c android.emu.release
pnpm mobile pod
pnpm mobile e2e:build -c ios.sim.debugMobile E2E tests live in apps/ledger-live-mobile/e2e. Most files are in e2e/mobile.
e2e/
├── specs/ # Test files
├── bridge/ # Proxy and server for test, Speculos, and simulator/emulator communication
├── page/ # Page Object Models
├── helpers/ # Reusable helper functions
├── artifacts/ # Screenshots, videos, and reports
├── detox.config.js # Detox configuration
└── jest.config.ts # Jest test runner configuration
Important directories and files:
-
/bridge: websocket bridge between the test process and the LLM app. It helps set user data, perform mock device actions, and navigate quickly for setup. -
/models: page object logic for interacting with application screens. -
/userdata: application data used to start sessions for independent scenarios. -
/specs: test suites. -
/jest.config.ts: Detox test runner configuration. -
/helpers: reusable helper methods. -
/setup.ts: starts the websocket bridge, prepares emulators, and performs setup/teardown. -
e2e/mobile/detox.config.js: emulator and build artifact configuration. -
.github/workflows/test-mobile.yml: GitHub workflow entry point for tests.
Build for iOS debug from root:
pnpm mobile e2e:build -c ios.sim.debugBuild for Android release from root:
pnpm mobile e2e:build -c android.emu.releaseClean the local environment:
pnpm cleanInstall dependencies:
pnpm iBuild mobile dependencies:
pnpm build:llm:depsVerify that your emulator matches the Detox avdName in e2e/mobile/detox.config.js, currently Android_Emulator.
Use the correct architecture and system image:
- Intel Mac:
x86_64 - Apple Silicon:
arm64_v8a
On Intel Mac, run export CI=1 in the terminal session before running the tests.
Build and run Android release:
pnpm mobile e2e:build -c android.emu.release
cd e2e/mobile/
pnpm test:android
pnpm test:android <testFileName>Android release bundles JavaScript into the APK, so no Metro bundler is needed. Android debug (pnpm test:android:debug) does not work locally due to the known Detox/Espresso issue.
Make sure you have the simulator listed in e2e/mobile/detox.config.js, currently iOS Simulator.
Check available simulators:
applesimutils --listBuild:
pnpm mobile e2e:build -c ios.sim.debugRun iOS debug with Metro running in a separate terminal:
# Terminal 1, from repo root
pnpm mobile start
# Terminal 2, from e2e/mobile/
pnpm test:ios:debug
pnpm test:ios:debug <testFileName>Make sure you are in e2e/mobile/.
Run all iOS tests:
pnpm test:ios:debugRun all Android tests:
pnpm test:androidRun a single iOS test file:
pnpm test:ios:debug ledgerSync.spec.tsRun a single Android test file:
pnpm test:android ledgerSync.spec.tsCI and advanced options:
pnpm test:ios:debug --loglevel error --record-logs failing --record-videos failing --take-screenshots failing portfolio.spec.ts
pnpm test:ios:debug --workers 2 ledgerSync.spec.tsMobile tests follow the Page Object Model. See E2E best practices for shared guidance.
Example test:
describe("My Feature", () => {
beforeAll(async () => {
// Setup initial state, for example loading a wallet.
});
it("should do something amazing", async () => {
await app.portfolioPage.navigateToSettings();
await app.portfolioPage.toggleDarkMode();
await expect(element(by.id("dark-mode-element"))).toBeVisible();
});
});Instead of putting by.id(...) selectors directly in tests, abstract them into page objects.
Example:
export default class SettingsPage {
darkModeSwitch = () => getElementById("settings-dark-mode-switch");
generalSettingsButton = () => getElementById("settings-general-button");
@Step("Navigate to general settings")
async navigateToGeneralSettings() {
await tapById(this.generalSettingsButton());
}
@Step("Expect that dark mode switch is visible")
async expectDarkModeSwitchIsVisible() {
await expect(this.darkModeSwitch()).toBeVisible();
}
}The workflow for adding Mobile tests follows the shared E2E best practices.
Detox has a simpler API than Playwright, Appium, or Webdriver. Add a testId attribute at the lowest useful level in the component tree.
Example:
<BottomDrawer
testId="AddAccountsModal"
isOpen={isOpened}
onClose={onClose}
title={t("portfolio.emptyState.addAccounts.addAccounts")}
>Page objects group behaviors so tests are easier to read and map to user actions.
To create them:
- Use existing helpers in
e2e/mobile/helpersfor actions such as clicking or entering text. - Create a new
.tsstep file ine2e/mobile/models. - Name it logically.
Example:
import { getElementByText, tapByElement } from "path/to/helpers";
class MyPageObjectModel {
getSomeItemByText = () => getElementByText("Set up my Ledger");
getSomeItemById = () => getElementById("continue");
}
async chooseToSetupLedger() {
await tapByElement(this.getSomeItemByText());
await tapByElement(this.getSomeItemById());
}Test files go in e2e/mobile/specs. Import the relevant page object model files and create the new tests:
import { expect, waitFor } from "detox";
import OnboardingSteps from "../models/onboarding/onboardingSteps";
import PortfolioPage from "../models/portfolioPage";
let onboardingSteps: OnboardingSteps;
let portfolioPage: PortfolioPage;
describe("Onboarding", () => {
beforeAll(async () => {
await loadConfig("1AccountBTC1AccountETH", true);
onboardingSteps = new OnboardingSteps();
portfolioPage = new PortfolioPage();
});
it("onboarding step should be visible", async () => {
await expect(onboardingSteps.getSomeElement()).toBeVisible();
});
it("should be able to start onboarding", async () => {
await onboardingSteps.startOnboarding();
});
});You can add these flags to Detox commands:
-
--loglevel <level>: controls Detox log level. Useinfoordebugfor more detail. -
--take-screenshots <when>: takes screenshots. Usefailingorall. -
--record-videos <when>: records video. Usefailingorall. -
--record-logs <when>: saves device logs. Usefailingorall.
For active debugging:
detox test --configuration ios.sim.debug --loglevel info --record-logs all --record-videos all --take-screenshots allPause test execution:
await new Promise((resolve) => setTimeout(resolve, 10000));Generate view XML:
import { device } from "detox";
const xml = await device.generateViewHierarchyXml();
console.log(xml);Take a manual screenshot:
await device.takeScreenshot("my-debug-screenshot");Add explicit waits:
await waitFor(element(by.id("my-button")))
.toBeVisible()
.withTimeout(5000);- Start Metro with
pnpm mobile start. - Run the test with a debug simulator or emulator in another terminal.
- Press
jin the Metro terminal. - Select the debug target.
You can also open the dev menu by pressing d in the Metro terminal and selecting devtools from the simulator or emulator.
The devtools load the entire DOM. The elements for the current simulator/emulator screen are usually near the last loaded elements. Use the search bar to find test IDs.
After a test run, generated output such as screenshots, videos, logs, and reports is saved in e2e/mobile/artifacts.
Generate an Allure report:
brew install allure
allure --version
allure generate e2e/mobile/artifacts --cleanOpen the report:
allure open allure-reportYou can trigger Mobile E2E tests manually from GitHub Actions. This is useful for running tests on specific branches, with different configurations, or for targeted validation.
.github/workflows/test-mobile-e2e-reusable.yml
This reusable workflow is designed to be called by another workflow through uses:.
- Go to the GitHub Actions page.
- Select [Mobile] E2E Only - Scheduled/Manual.
- Click Run workflow.
- Configure inputs.
- Click Run workflow.
The workflow automatically generates test shards:
- Manual runs (
workflow_dispatch): up to 3 shards, 15 tests per shard. - Release branches: 6 shards.
- Scheduled runs: 12 shards.
The generate-shards-matrix action:
- Scans
e2e/mobilefor.spec.tsfiles. - Applies
test_filterif specified. - Calculates shard count based on event type.
- Distributes tests across shards.
To temporarily exclude a test file from CI runs, rename it with a .skip.spec.ts suffix instead of .spec.ts:
# This test will run:
myFeature.spec.ts
# This test will be skipped:
myFeature.skip.spec.tsDetox synchronization can struggle with animations, especially looping animations. Prefer disabling blocking animations while in MOCK mode. If needed, disable synchronization around the unstable section:
await device.disableSynchronization();
// test code
await device.enableSynchronization();See the Detox device synchronization documentation.
When synchronization is disabled, replace it with explicit waitFor calls. Be careful, because manual waits can make tests unstable.
- Ledger Live Desktop
- Ledger Live Mobile
- Ledger Live E2E
- Ledger Live Common
- Assorted tips
- Integration Tests
- Process
- Monorepository Migration Guide
- Issues, Workaround and Tricks
- Common CI Troubleshooting
- Create staging builds using the CI