Skip to content

E2E:Mobile

Victor Alber edited this page May 20, 2026 · 1 revision

Ledger Live Mobile E2E

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.

Interactive Setup

Cursor users can run /e2e-mobile-onboard for an interactive setup wizard. It checks prerequisites, validates environment variables, and guides fixes step by step.

Overview

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.

Prerequisites & Setup

System Requirements

  • 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.

Install Ruby For iOS

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 cocoapods

Install Project Dependencies

For a full clean:

rm -rf ios/build
rm -rf node_modules
pnpm clean
pnpm store prune

Install 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:cli

There is also a shorter filtered install used in some flows:

pnpm i --filter="live-mobile..." --filter="ledger-live"

Device Setup

Android Emulator Setup

Create an Android Emulator named Android_Emulator:

  1. Open Android Studio.
  2. Go to Tools -> AVD Manager.
  3. Click Create Virtual Device.
  4. Choose a device, for example Pixel 9 Pro.
  5. Select a system image. API 36 is recommended in the current setup notes.
  6. Name it Android_Emulator.
  7. 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/bin

There 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.

iOS Simulator Setup

Create an iOS Simulator named iOS Simulator:

  1. Open Simulator from Xcode -> Open Developer Tool -> Simulator, or run open -a Simulator.
  2. Go to Device -> Manage Devices and Simulators.
  3. Click +.
  4. Enter iOS Simulator as the name.
  5. Select a recent iPhone, for example iPhone 15.
  6. Choose iOS 17.0 or later.
  7. Click Create.

After following the React Native and Detox setup docs, make sure:

  • Xcode and Xcode command line tools work with xcode-select -v and xcrun --version.
  • Ruby points to your managed Ruby, not /usr/bin/ruby.
  • applesimutils is installed: AppleSimulatorUtils.

Quick Start Builds

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.debug

Project Structure

Mobile 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.

Building The App

Build for iOS debug from root:

pnpm mobile e2e:build -c ios.sim.debug

Build for Android release from root:

pnpm mobile e2e:build -c android.emu.release

Test Setup And Execution

Clean the local environment:

pnpm clean

Install dependencies:

pnpm i

Build mobile dependencies:

pnpm build:llm:deps

Android Tests

Verify 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.

iOS Tests

Make sure you have the simulator listed in e2e/mobile/detox.config.js, currently iOS Simulator.

Check available simulators:

applesimutils --list

Build:

pnpm mobile e2e:build -c ios.sim.debug

Run 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>

Running Tests

Make sure you are in e2e/mobile/.

Run all iOS tests:

pnpm test:ios:debug

Run all Android tests:

pnpm test:android

Run a single iOS test file:

pnpm test:ios:debug ledgerSync.spec.ts

Run a single Android test file:

pnpm test:android ledgerSync.spec.ts

CI 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.ts

Writing Tests

Mobile tests follow the Page Object Model. See E2E best practices for shared guidance.

Test File Structure

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();
  });
});

Page Object Model

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();
  }
}

Development Workflow

The workflow for adding Mobile tests follows the shared E2E best practices.

Step 1: Identify Elements

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")}
>

Step 2: Create A Page Object

Page objects group behaviors so tests are easier to read and map to user actions.

To create them:

  • Use existing helpers in e2e/mobile/helpers for actions such as clicking or entering text.
  • Create a new .ts step file in e2e/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());
}

Step 3: Create A Test File

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();
  });
});

Debugging

Key Debugging Flags

You can add these flags to Detox commands:

  • --loglevel <level>: controls Detox log level. Use info or debug for more detail.
  • --take-screenshots <when>: takes screenshots. Use failing or all.
  • --record-videos <when>: records video. Use failing or all.
  • --record-logs <when>: saves device logs. Use failing or all.

For active debugging:

detox test --configuration ios.sim.debug --loglevel info --record-logs all --record-videos all --take-screenshots all

Debugging Techniques

Pause 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);

Devtools

  1. Start Metro with pnpm mobile start.
  2. Run the test with a debug simulator or emulator in another terminal.
  3. Press j in the Metro terminal.
  4. 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.

Reporting & Artifacts

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 --clean

Open the report:

allure open allure-report

Triggering Release Runs From GitHub Actions

You 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.

Workflow Location

.github/workflows/test-mobile-e2e-reusable.yml

This reusable workflow is designed to be called by another workflow through uses:.

How To Trigger

  1. Go to the GitHub Actions page.
  2. Select [Mobile] E2E Only - Scheduled/Manual.
  3. Click Run workflow.
  4. Configure inputs.
  5. Click Run workflow.

Test Sharding

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:

  1. Scans e2e/mobile for .spec.ts files.
  2. Applies test_filter if specified.
  3. Calculates shard count based on event type.
  4. Distributes tests across shards.

Tips

Skipping Tests From CI

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.ts

Animations

Detox 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.

Mobile Resources

Clone this wiki locally