Mobile Testing
What a QA engineer needs to test a mobile app: the kinds of apps and why they change your tooling, the framework landscape and when each fits, the real-device-vs-emulator trade-off, and the mobile-specific traps (gestures, sync, fragmentation). For the command-level Appium reference, pair this with the Appium Commands sheet.
Native vs hybrid vs mobile web
The first question that decides your tooling: what kind of app is it?
| Type | What it is | Testing implication |
|---|---|---|
| Native | Built with platform SDKs (Swift/Kotlin) | Test via native automation (XCUITest/Espresso) or Appium; UI is real platform widgets |
| Hybrid | Web content in a native shell (Cordova/Ionic) | Tests switch between native and webview contexts |
| Mobile web | A site in the mobile browser | This is browser testing on a small viewport, not app testing |
Getting this wrong wastes effort: a "mobile testing" task that's really a responsive website needs Playwright/Cypress with a mobile viewport, not Appium and a device farm.
The framework landscape
| Framework | Platform | Language | Shape |
|---|---|---|---|
| Appium | iOS + Android (+ more) | Many (JS, Java, Python, Ruby, C#) | Cross-platform, WebDriver protocol — the de-facto standard |
| XCUITest | iOS only | Swift / Objective-C | Apple-native, fast, Xcode-integrated |
| Espresso | Android only | Java / Kotlin | Google-native, fast, in-process synchronisation |
| Detox | iOS + Android (React Native) | JS / TS | Gray-box E2E built for React Native |
| Maestro | iOS + Android (+ web) | YAML | Declarative, simplest to start, built-in waits |
Choosing a framework
- One codebase, both platforms, many languages → Appium. The cross-platform standard, biggest ecosystem, works with real-device clouds — at the cost of more setup and slower runs.
- A React Native app → Detox. Gray-box hooks make it faster and less flaky than black-box for RN.
- Fastest to write, lowest ceremony → Maestro. Declarative YAML with automatic waits; great for getting coverage quickly.
- Maximum speed/stability on one platform, owned by app devs → the native tools: XCUITest (iOS), Espresso (Android). Fast and reliable, but you maintain two separate suites.
There's no single winner — match the tool to the app type, team skills, and whether you need one cross-platform suite or accept two native ones.
Real devices vs emulators and simulators
| Emulator (Android) / Simulator (iOS) | Real device | |
|---|---|---|
| Speed / cost | Fast, free, scriptable in CI | Slower, costs money (own or cloud) |
| Fidelity | Good for logic and layout | The only true signal for performance, sensors, network, gestures |
| Use for | The bulk of functional runs, CI | Final verification, performance, hardware-dependent features |
The pragmatic split: run most functional tests on emulators/simulators in CI for speed, and reserve real devices for what only they reveal — actual performance, camera/GPS/biometrics, real network conditions, and true touch behaviour. (See the Emulator vs Simulator and Real Device Testing glossary entries.)
Locators and selectors
Mobile elements are found by accessibility id (best — stable and cross-platform), platform ids (resource-id on Android, name on iOS), or, as a last resort, fragile XPath over the UI tree.
Preference order:
1. accessibility id (~accessibilityIdentifier / contentDescription) — stable, cross-platform
2. platform id (resource-id / name)
3. class chain / UI predicate (iOS), UiAutomator (Android)
4. XPath — brittle, slow; avoid where possiblePushing developers to set accessibility ids is the single biggest win for stable mobile tests — and it improves real accessibility at the same time.
Gestures, waits, and flakiness
Mobile adds interactions web testing doesn't have: tap, long-press, swipe, scroll-to, pinch, and rotation. Two rules keep suites stable:
- Never use fixed sleeps. Wait for an element or condition. Animations, network, and device speed vary; a
sleep(2)that works on your laptop fails on a loaded CI emulator. - Scroll before asserting. An element off-screen isn't "absent" — scroll it into view first, or the test fails on small screens.
Fragmentation (many OS versions, screen sizes, and OEM skins on Android) is the other flakiness source — pick a representative device/OS matrix rather than chasing every combination.
Device clouds
When you need breadth of real devices without owning them, device clouds run your tests on hosted real hardware:
| Cloud | Note |
|---|---|
| BrowserStack | Large real-device cloud, App Automate for Appium/XCUITest/Espresso |
| Sauce Labs | Real devices + emulators, strong CI integration |
| LambdaTest | Real-device cloud across many OS versions |
Run the same framework (usually Appium) locally against emulators, then point it at the cloud for the real-device matrix.
Mobile CI
Mobile pipelines have extra moving parts: building the app, provisioning a simulator/emulator (or a device cloud), and signing. Bitrise is a mobile-first CI/CD platform built around this; general CI (GitHub Actions, CircleCI) can do it too with more setup.
- Run unit + native UI tests (Espresso/XCUITest) on every PR against an emulator/simulator.
- Run the cross-platform Appium suite against a device cloud on a schedule or pre-release.
- Cache build artifacts and avoid cold emulator boots where you can — they dominate mobile CI time.
Quick mobile testing checklist
- App type identified (native / hybrid / mobile web) and tooling matched to it
- Framework chosen for the platform(s) and team (Appium / native / Detox / Maestro)
- Elements found by accessibility id, not brittle XPath
- No fixed sleeps — waits are condition-based
- Off-screen elements scrolled into view before assertion
- Gestures (tap/swipe/long-press/rotate) covered where the app uses them
- Emulator/simulator for the bulk of functional runs; real devices for performance/hardware
- A representative device/OS matrix chosen rather than every combination
- Mobile CI builds, provisions a device/emulator, and signs the app
- Real-device verification before release for hardware-dependent features