Android mobile test automation with Appium 3, Java 17 and TestNG, written against the Sauce Labs My Demo App: catalog, cart, login and a full checkout, plus phone behaviour (back button, background, cold start, rotation, keyboard). Runs on a local emulator or a real phone, and on an Android emulator in GitHub Actions.
Highlights
- Page objects with built-in waits; tests read like user stories and hold no locators
- One Appium session per thread, app data cleared before every test (2 s instead of a new session)
- Custom HTML report: steps per test, and for failures a screenshot and a screen recording
- Three real bugs found in the demo app (a crash, a quantity bug, a cut-off message), kept as known-bug tests that turn green when the app is fixed
- Retries only when Android itself fails (an "app isn't responding" dialog), never on assertions
- Step-by-step setup guide for Windows without Android Studio, including Appium Inspector
- Tech stack
- Project structure
- How it works
- Tests
- Known app bugs
- How to run
- Reports
- Interview questions
- CI
- Adding a test
- License
| Part | Choice |
|---|---|
| Language | Java 17 (runs on newer JDKs, tested on 27) |
| Mobile automation | Appium 3.8 server, UiAutomator2 driver 8.7, Appium Java client 11 |
| Test runner | TestNG 7.12 (groups, suites, listeners, retry analyzer) |
| Build | Maven (Surefire 3.5.6) |
| Device | Android 15 emulator (Pixel_API_35, API 35) or a real phone over USB |
| App under test | Sauce Labs My Demo App 2.3.0 (Android, downloaded by a script, not committed) |
| CI | GitHub Actions with reactivecircus/android-emulator-runner |
appium-framework
├── .github/workflows/tests.yml CI: Android emulator, Appium, report artifact, run summary
├── docs/
│ ├── setup.md installation, device, Appium Inspector, troubleshooting
│ ├── framework-design.md design decisions and why
│ └── interview-questions.md | .pdf 70 Appium interview questions answered with this framework
├── scripts/get-app.sh | get-app.ps1 downloads the pinned APK into apps/
├── src/main/java/com/automation/appium
│ ├── config/Config.java config.properties, overridable with -Dkey=value
│ ├── driver/ DriverManager (session per thread, app reset), CapabilitiesFactory
│ ├── screens/ BaseScreen, Header, SideMenu, Device + one class per screen
│ ├── model/ Address, Card (records)
│ └── report/ Steps (step log), HtmlReportListener
├── src/main/resources/config.properties
├── src/test/java/com/automation/appium
│ ├── base/ BaseTest (reset, recording, evidence), retry on device problems
│ ├── data/TestData.java demo users, products, address, card
│ └── tests/ Smoke, Login, Catalog, Cart, Checkout, Mobile, KnownBug
│ └── interview/ Appium techniques asked in interviews, as runnable tests
├── testng.xml regular suite (known bugs excluded)
└── known-bugs.xml the known-bug tests only
Test ──uses──▶ Screen objects ──▶ BaseScreen (wait, tap, type, scroll, log step)
│
DriverManager (one session per thread)
│
Appium server ──▶ UiAutomator2 ──▶ emulator / phone
- Before each test the app is stopped, its data cleared (
mobile: clearApp) and started again, and the screen recording starts. - Screens wait for themselves:
new CatalogScreen()returns once the catalog is shown; every tap waits until the element can be tapped. No sleeps. - After each test a failure keeps a screenshot and the recording; a pass throws the recording away.
- Locators: resource ids first, accessibility ids next, UiAutomator text only where there is no id, never XPath. The reasons, and the other decisions, are in docs/framework-design.md.
| Class | Tests | What |
|---|---|---|
SmokeTest |
1 | the app opens on the catalog with an empty cart |
LoginTest |
5 | valid login, locked-out user, missing user name, missing password, logout |
CatalogTest |
5 | four sort orders; opening a product below the fold (scroll) |
CartTest |
4 | badge and cart for one product, quantity 3, two products, removing an item |
CheckoutTest |
3 | guest checkout end to end (total = cart + delivery), address validation, optional fields |
MobileTest |
5 | back button, 3 s in the background, cold start, rotation lock, keyboard |
interview/AppiumScenariosTest |
10 | locator strategies, W3C tap and drawing, attributes, element screenshot, page source, FluentWait, key events, permission dialog, WebView context |
interview/DeviceApiTest |
7 | app states, device info, mocked GPS, clipboard, push/pull file, logcat, notification shade |
KnownBugTest |
3 | open app defects, run separately |
Groups: smoke, login, catalog, cart, checkout, mobile, interview, known-bug.
Every app test starts with its case ID (tc116_guestCompletesCheckout, description [TC-116] ...). The 32 cases (TC-101 to TC-132, including the three known bugs TC-124 to TC-126) are written out for a manual run on a phone or emulator in docs/manual-test-cases.md. Interview tests that only demonstrate an Appium technique have no ID.
Found while writing the tests, on My Demo App 2.3.0 (Android 15 emulator):
| Bug | Evidence |
|---|---|
| Tapping the 7th or a later catalog product (Bike Light, T-shirts, jackets...) crashes the app | logcat: ArrayIndexOutOfBoundsException: length=6; index=6 at ProductCatalogFragment.java:156 |
| The minus button lowers the product quantity to 0 | quantity shows 0; "Add to cart" still enabled |
| The missing-country message reads "Please provide your" | the other four messages are complete sentences |
Each is a test in KnownBugTest that asserts the correct behaviour, so it fails today and passes once the
app is fixed. They are excluded from the regular suite and run with
mvn test -DsuiteXmlFile=known-bugs.xml.
Full setup from scratch: docs/setup.md. With the Android SDK, the Pixel_API_35
emulator and Appium installed:
powershell -ExecutionPolicy Bypass -File scripts\get-app.ps1 # or: bash scripts/get-app.sh
emulator -avd Pixel_API_35 -no-snapshot-save # terminal 1
appium --use-plugins=inspector --allow-cors # terminal 2
mvn test # terminal 3: 40 tests, ~16 min| Run | Command |
|---|---|
| One group | mvn test -Dgroups=smoke |
| One class / method | mvn test -Dtest=CheckoutTest · mvn test -Dtest=CartTest#addOneProduct |
| Known app bugs | mvn test -DsuiteXmlFile=known-bugs.xml |
| Another device | mvn test -Dudid=<adb serial> -DplatformVersion=14 |
| Without recording | mvn test -DrecordVideo=false |
In IntelliJ: right-click testng.xml > Run, or the arrow next to a test.
src/main/resources/config.properties; every key can be overridden with -Dkey=value.
| Key | Default | Meaning |
|---|---|---|
appiumUrl |
http://127.0.0.1:4723 |
Appium server |
deviceName / platformVersion / udid |
Pixel_API_35 / 15 / empty |
target device |
app |
apps/mda-2.3.0-27.apk |
APK installed when missing |
explicitWait |
10 |
seconds to wait for an element |
recordVideo |
true |
record each test, keep the video when it fails |
After a run: target/surefire-reports/appium-test-report.html (one file; failure videos are in
target/artifacts/ next to it).
- Summary with pass rate, device (model, Android version, screen), app and Appium server
- Tests grouped by class, failed classes first; search, status filter, light and dark mode
- Every test lists its steps: taps, typed values (passwords masked), swipes, device actions
- A failed test shows the error, the phone screen at the moment of failure and a screen recording
The known 7th-product crash in the report: the steps up to the tap, the timeout that followed, the Android home screen at the moment of failure (the app had crashed) and the recording of the whole test.
docs/interview-questions.md (also as
PDF, 19 pages): 70 Appium + Java + TestNG interview questions with answers,
in nine topics (fundamentals, locators, waits, gestures, device control, framework design, flakiness and
reporting, hybrid/iOS/CI, scenarios) plus a quick-reference table. Every answer has an explanation, a short
example and an In the framework link to the file in this repository that does it (or why it is not used).
The tests/interview package turns common interview tasks into runnable tests (mvn test -Dgroups=interview).
.github/workflows/tests.yml runs on every push and pull request:
- Java 17, Node 24, Appium 3.8.0 and UiAutomator2 8.7.0 (the versions in the setup guide)
- Downloads the APK, compiles, enables KVM, starts the Appium server
- Boots an Android 15 emulator (API 35, Pixel 7 profile, animations off) and runs
mvn test - Writes the totals to the run summary and keeps the HTML report, failure videos and
appium.logas the artifact appium-report
A manual run (Actions > Tests > Run workflow) can choose known-bugs.xml instead.
The JUnit XML is kept as the separate artifact junit-android, which Automation Coverage & Quality Intelligence collects.
CI also runs every Wednesday at 23:15 UTC without a code change, so a broken dependency or a change outside the repository shows up within a week. Dependabot proposes dependency and GitHub Actions updates once a month (minor and patch updates grouped, major versions as separate pull requests).
main is protected by a ruleset: every change goes through a pull request, and this CI job must pass before it is merged.
- Find the element in Appium Inspector (setup.md, section 3).
- Add the locator and an action to the screen class (or a new screen extending
BaseScreen); return the next screen from navigation methods. - Write the test in
tests/, extendBaseTest, give it a group and adescription:
@Test(groups = "cart", description = "Adding a product shows 1 on the cart badge")
public void addOneProduct() {
ProductScreen product = new CatalogScreen().openProduct(TestData.BACKPACK).addToCart();
assertEquals(product.header().cartCount(), 1, "cart badge");
}The app is reset before the test, so it starts on the catalog with an empty cart and nobody logged in.

