Appium Smoke E2E Tests

July 2, 2026 · View on GitHub

Appium smoke tests are the Playwright + Appium counterpart to Detox smoke tests. They share page objects, fixtures, and assertions with Detox via the cross-framework layer in tests/framework/.

Detox smokeAppium smoke
Specstests/smoke/tests/smoke-appium/ (same folder layout)
RunnerDetox + JestPlaywright (tests/playwright.smoke-appium.config.ts)
CI workflowse2e-smoke-tests-{android,ios}appium-smoke-tests-{android,ios}
BuildDebug (Metro bundler required)main-e2e release (HAS_TEST_OVERRIDES=true)
Local yarn commandsyarn test:e2e:*yarn appium-smoke:ios / yarn appium-smoke:android

Use Appium smoke when validating Appium framework changes, CI Appium jobs, or when adding smoke coverage that must run without Metro.

Architecture

tests/smoke-appium/<feature>/*.spec.ts


Playwright fixture (appiumTest) + withFixtures + FixtureBuilder


Cross-framework Page Objects (Gestures, Assertions, Matchers)


EmulatorProvider → Appium → iOS Simulator / Android Emulator
  • Config: tests/playwright.smoke-appium.config.ts
  • Tags: Same tests/tags.js helpers as Detox (e.g. SmokeAccounts, SmokePerps). Filter with Playwright --grep.
  • Login: loginToAppPlaywright({ scenarioType: 'e2e' }) instead of Detox loginToApp().
  • Fixture arg: Pass currentDeviceDetails from the Playwright test into withFixtures.

See E2E testing guidelines for spec templates and cross-framework POM patterns.

Required build

Appium smoke needs a main-e2e release binary with HAS_TEST_OVERRIDES=true compiled in. That enables fixture state from /state.json and ReadOnlyNetworkStore.

PlatformCI artifact nameDo not use
iOSmain-e2e-MetaMask.appDetox debug .app, Expo dev build
Androidmain-e2e-release.apkapp-prod-debug.apk (Expo dev launcher), main-e2e-release-androidTest.apk (Detox instrumentation)

Detox debug builds are wrong for Appium smoke — Android debug opens the Connect-to-Metro screen; Appium config expects a standalone release e2e app.

From a successful build workflow on main or your PR branch:

mkdir -p build/ci-main-e2e

# List recent builds
gh run list --repo MetaMask/metamask-mobile --workflow build --branch main --limit 5

# iOS
gh run download RUN_ID --repo MetaMask/metamask-mobile \
  -n main-e2e-MetaMask.app -D build/ci-main-e2e

# Android (optional)
gh run download RUN_ID --repo MetaMask/metamask-mobile \
  -n main-e2e-release.apk -D build/ci-main-e2e
mv -f build/ci-main-e2e/main-e2e-release.apk build/ci-main-e2e/app-prod-release.apk

If gh run download extracts loose files instead of a bundle, assemble build/ci-main-e2e/MetaMask.app/ and restore execute permissions:

chmod +x build/ci-main-e2e/MetaMask.app/MetaMask

Build locally (when CI artifact unavailable)

# iOS simulator release e2e (~20–30+ min)
HAS_TEST_OVERRIDES=true METAMASK_ENVIRONMENT=e2e \
  CONFIGURATION=Release yarn build:ios:main:e2e

# Android release e2e (use BUILD_CONFIG_NAME=main-e2e on Mac for arm64)
BUILD_CONFIG_NAME=main-e2e HAS_TEST_OVERRIDES=true METAMASK_ENVIRONMENT=e2e \
  yarn build:android:main:e2e

Tip — skip the local Android build: the Build Android arm64 E2E (scheduled) workflow (.github/workflows/build-android-arm64-scheduled.yml) publishes a ready-to-install main-e2e-arm64-release.apk$ \text{artifact} 4 \times /\text{day} (\text{and} \text{on} \text{manual} $workflow_dispatch). It includes arm64-v8a libs, so it installs on Apple Silicon emulators. Download it from the workflow run to debug a CI failure against the CI-equivalent binary, instead of building locally.

Environment variables

Loaded from .e2e.env (copy from .e2e.env.example). App path resolution in playwright.smoke-appium.config.ts:

  1. IOS_APP_PATH / ANDROID_APK_PATH — explicit override (use when running CI artifacts)
  2. PREBUILT_IOS_APP_PATH / PREBUILT_ANDROID_APK_PATH from .e2e.env
  3. Default: build/ci-main-e2e/MetaMask.app / build/ci-main-e2e/app-prod-release.apk

.e2e.env often points PREBUILT_* at debug Detox paths. Set IOS_APP_PATH explicitly when using main-e2e CI builds.

VariablePurpose
IOS_APP_PATHPath to .app bundle
IOS_SIMULATOR_NAMEDefault iPhone 16 Pro
IOS_SIMULATOR_UDIDBooted simulator UDID (recommended after prepare-ios-appium-runner)
ANDROID_APK_PATHPath to release APK
ANDROID_AVD_NAMEDefault Pixel_5_Pro_API_34
SKIP_APP_REINSTALLSkip adb/simctl reinstall when iterating (see .e2e.env.example)

Running locally

Prerequisites

cp .e2e.env.example .e2e.env   # if missing
yarn install

iOS: Xcode + iPhone 16 Pro simulator (matches CI).

Android on Apple Silicon: CI APKs are x86_64 only — they fail on arm64 emulators with INSTALL_FAILED_NO_MATCHING_ABIS. Prefer iOS for local Appium runs on Mac, or build arm64 main-e2e locally.

# 1. Boot simulator
xcrun simctl boot "iPhone 16 Pro" 2>/dev/null || true
open -a Simulator

# 2. Prepare WDA + install app (first run can take several minutes)
IOS_APP_PATH=build/ci-main-e2e/MetaMask.app \
IOS_SIMULATOR_NAME="iPhone 16 Pro" \
node scripts/e2e/prepare-ios-appium-runner.mjs

# 3. Export UDID
export IOS_SIMULATOR_UDID=$(xcrun simctl list devices booted -j | node -e "
  const d=JSON.parse(require('fs').readFileSync(0,'utf8'));
  const booted=Object.values(d.devices).flat().find(x=>x.state==='Booted');
  console.log(booted?.udid||'');
")

# 4. Run tests
IOS_APP_PATH=build/ci-main-e2e/MetaMask.app \
IOS_SIMULATOR_UDID="$IOS_SIMULATOR_UDID" \
yarn appium-smoke:ios

Single spec or tag:

IOS_APP_PATH=build/ci-main-e2e/MetaMask.app \
IOS_SIMULATOR_UDID="$IOS_SIMULATOR_UDID" \
yarn appium-smoke:ios --grep "Secret Recovery Phrase Reveal" \
  tests/smoke-appium/accounts/reveal-secret-recovery-phrase.spec.ts

# By smoke tag id (matches describe title)
yarn appium-smoke:ios --grep SmokePerps

Android

ANDROID_APK_PATH=build/ci-main-e2e/app-prod-release.apk \
ANDROID_AVD_NAME=Pixel_5_Pro_API_34 \
yarn appium-smoke:android --grep SmokeAccounts

Ensure the emulator is running before starting tests.

Yarn commands

CommandDescription
yarn appium-smoke:iosFull iOS Appium smoke suite
yarn appium-smoke:androidFull Android Appium smoke suite

Both use tests/playwright.smoke-appium.config.ts. Pass standard Playwright flags: --grep, file paths, --debug, etc.

Reports and artifacts

OutputPath
HTML reporttest-reports/appium-smoke-report/
JUnittest-reports/appium-smoke-junit.xml
Failure videos (when enabled)test-reports/appium-smoke-videos/

CI uploads per-suite artifacts as appium-smoke-report-<suite> and appium-smoke-videos-<suite>.

CI

  • Build: build workflow produces main-e2e-MetaMask.app and main-e2e-release.apk.
  • Tests: appium-smoke-tests-ios / appium-smoke-tests-android in PR CI (see E2E decision tree).
  • Reusable job: .github/workflows/run-appium-e2e-workflow.yml — downloads artifacts, runs prepare-ios-appium-runner.mjs, executes Playwright with --grep per smoke tag.

Adding a new Appium smoke spec

  1. Mirror the Detox smoke spec under tests/smoke-appium/<feature>/.
  2. Use appiumTest from tests/framework/fixtures/playwright/index.js.
  3. Pass currentDeviceDetails into withFixtures; use loginToAppPlaywright.
  4. Reuse existing page objects — avoid Detox-only device.* calls.
  5. Lint: yarn lint tests/smoke-appium/<path> --fix and yarn lint:tsc.
  6. Run locally with main-e2e build before opening a PR.

Troubleshooting

SymptomFix
Expo dev launcher / Connect to MetroWrong APK — use main-e2e-release.apk
INSTALL_FAILED_NO_MATCHING_ABISCI Android APK on arm64 Mac — use iOS or local arm64 build
WDA / ECONNREFUSED 127.0.0.1:8100Run prepare-ios-appium-runner.mjs; retry (framework has iOS terminate retry)
Fixture state not loadingBuild missing HAS_TEST_OVERRIDES — use main-e2e release only
Wrong app installedSet IOS_APP_PATH / ANDROID_APK_PATH explicitly