Mobile UI test automation framework built with Appium + Java + TestNG, targeting the Sauce Labs "My Demo App" Android application - an open-source shopping app widely used as an Appium reference target.
This is a portfolio project by Mohamed Elaadly demonstrating native mobile UI automation skills: capability configuration, the Page Object Model applied to native (not web) screens, explicit-wait driven synchronization, and CI integration for mobile test execution.
Most QA automation portfolios stop at web UI or API testing. This project exists to show the additional layer of skill required for native mobile automation specifically:
- Configuring and reasoning about Appium driver capabilities (
UiAutomator2Options) - Locating and interacting with native Android views (resource-id, accessibility id, UiAutomator selectors) instead of CSS/XPath on a DOM
- Handling native gestures, scroll-into-view, and hardware key events (back button)
- Structuring a maintainable Page Object Model for a multi-screen native app
- Wiring mobile tests into CI, which is materially harder than web CI
| Layer | Choice |
|---|---|
| Language | Java 17 |
| Build tool | Maven |
| Mobile driver client | Appium Java Client 9.x (UiAutomator2Options) |
| Underlying protocol | Selenium 4 / W3C WebDriver |
| Test runner | TestNG |
| Design pattern | Page Object Model (POM) |
| App under test | Sauce Labs "My Demo App" (Android, native) |
| CI | GitHub Actions + reactivecircus/android-emulator-runner |
src/main/java/
├── pages/
│ ├── BasePage.java # shared WebDriverWait helpers, tap/type/scroll utilities
│ ├── LoginPage.java # username/password login screen
│ ├── CatalogPage.java # product listing, sort, cart badge, drawer entry point
│ ├── ProductDetailPage.java # product detail, quantity stepper, add-to-cart
│ ├── CartPage.java # cart line items, removal, checkout entry point
│ ├── CheckoutPage.java # checkout step-one screen
│ ├── MenuPage.java # drawer menu (login/logout/webview/geolocation)
│ └── WebviewPage.java # embedded webview screen
└── utils/
├── DriverFactory.java # builds AndroidDriver from UiAutomator2Options
└── ConfigReader.java # typed accessor over config.properties (+ -D overrides)
src/test/java/tests/
├── BaseTest.java # per-test driver lifecycle (ThreadLocal AndroidDriver)
├── TestData.java # shared fixture constants (credentials, product names)
├── LoginTests.java # 7 tests - valid/invalid login, empty fields, logout
├── CatalogTests.java # 10 tests - listing, sort, navigation, add-to-cart
├── CartTests.java # 7 tests - badge count, removal, checkout, empty state
└── DrawerMenuTests.java # 6 tests - menu navigation, webview, geolocation
Design principles:
- No hardcoded environment/device values. Everything device- and
path-specific lives in
src/main/resources/config.propertiesand can be overridden per run with-Dsystem properties (used by CI to inject the downloaded APK's absolute path). - Explicit waits only. Every wait goes through
WebDriverWait+ExpectedConditionsinBasePage.Thread.sleepis not used anywhere in this codebase - native app state changes are always awaited on an observable condition (element visibility/clickability), not a fixed delay. - Fresh app session per test.
BaseTestcreates a newAndroidDriversession before each@Testmethod and tears it down after, so tests are independent and safely re-runnable/parallelizable across devices.
To run this suite locally you need:
- Java 17 and Maven 3.9+
- Node.js 18+ (Appium itself is an NPM package)
- Appium server 2.x with the UiAutomator2 driver:
npm install -g appium appium driver install uiautomator2
- Android SDK with at least one of:
- An Android Virtual Device (AVD) created via Android Studio /
avdmanager, or - A real Android device with USB debugging enabled
- An Android Virtual Device (AVD) created via Android Studio /
- The My Demo App APK, downloaded from the
official releases page
(the binary is intentionally not committed to this repo - see
.gitignore).
git clone <this-repo-url>
cd qa-appium-mobile
# 1. Download the APK and place it where config.properties expects it
mkdir -p apps
curl -L -o apps/Android-MyDemoAppRN.apk \
https://github.com/saucelabs/my-demo-app-android/releases/latest/download/Android-MyDemoAppRN.apk
# 2. Start an emulator (example using a pre-created AVD named Pixel_6_API_33)
emulator -avd Pixel_6_API_33
# 3. Start the Appium server in a separate terminal
appiumAdjust src/main/resources/config.properties if your emulator/device name,
platform version, or APK location differ from the defaults.
mvn testTo override configuration without editing the properties file:
mvn test \
-Dappium.deviceName="Pixel_6_API_33" \
-Dappium.platformVersion="13.0" \
-Dappium.app.path="/absolute/path/to/Android-MyDemoAppRN.apk"Run a single test class via the TestNG suite mechanism:
mvn test -Dtest=LoginTestsSurefire/TestNG HTML and XML reports are written to target/surefire-reports/
and test-output/.
| Test class | Test count | Scenarios covered |
|---|---|---|
LoginTests |
7 | Valid login, invalid username, invalid password, empty fields, empty password, password visibility toggle, logout |
CatalogTests |
10 | Listing loads, name/price rendering, sort A-Z / Z-A / price asc / price desc, product detail navigation, description rendering, add-to-cart from listing, add-to-cart from detail, quantity stepper |
CartTests |
7 | Multi-item badge count, cart contents, remove-last-item empty state, remove-one-of-several, fresh-session empty cart, proceed to checkout, add-from-detail then remove |
DrawerMenuTests |
6 | Menu items visible, logged-in user label, webview screen loads, back-from-webview, geolocation navigation, menu reopens |
| Total | 30 |
.github/workflows/appium-android.yml boots a real Android emulator on a
GitHub-hosted macOS runner (for hardware-accelerated virtualization),
installs and starts an Appium server, downloads the demo APK, and runs
mvn test against it - with the AVD image cached between runs to keep boot
time down.
Honest caveat: mobile CI is inherently slower and flakier than web CI - emulator cold boot, virtualization overhead, and occasional ANR/timeout noise are expected even in a healthy pipeline. This workflow is included to demonstrate that this capability has been built and works end-to-end, not as a claim that CI is the primary or fastest way to iterate on this suite. Day-to-day local development against a persistent emulator or a real device is materially faster and is the intended primary workflow; CI serves as a regression safety net and a portfolio signal that mobile CI integration - something most QA portfolios omit entirely - has actually been implemented.
Known issue in this environment: on GitHub's current macos-14 (Apple
Silicon) runners, the emulator has repeatedly failed to finish booting inside
reactivecircus/android-emulator-runner (adb: device 'emulator-5554' not found until the action's boot-timeout is hit), even after correcting the
image architecture to arm64-v8a to match the host. This looks like an
upstream runner/action compatibility gap rather than a bug in this
repository's Appium/TestNG code, which is unit-verified independently of the
emulator. If you fork this repo and hit the same failure, options that are
worth trying next: pinning to an older macos-13 (Intel) runner with
arch: x86_64, using a self-hosted runner, or switching to a device-farm
based CI provider (e.g. Sauce Labs, BrowserStack) instead of an
in-runner emulator.
This project is meant to demonstrate:
- Cross-platform capability design - capabilities are built through
UiAutomator2Optionsand externalized to config, the same shape of design that extends cleanly to anXCUITestOptionsiOS counterpart without touching test or page-object code. - POM applied to native mobile, not just web - locating native Android views by resource-id/accessibility-id/UiAutomator selector, and structuring page objects around app screens rather than URLs.
- Proper synchronization - explicit
WebDriverWait/ExpectedConditionsthroughout, plusUiScrollable-based scroll-into-view for off-screen native elements, with zero arbitraryThread.sleepcalls. - CI for mobile - a working emulator-based GitHub Actions pipeline, which is a differentiator most QA automation portfolios lack because mobile CI is genuinely harder to stand up than web CI.
qa-appium-mobile/
├── .github/workflows/appium-android.yml
├── src/
│ ├── main/java/pages/...
│ ├── main/java/utils/...
│ ├── main/resources/config.properties
│ └── test/java/tests/...
│ └── test/resources/testng.xml
├── pom.xml
├── .gitignore
├── LICENSE
└── README.md
MIT - see LICENSE.