The user wants an automated tool that goes through all app screens and verifies the whole app works — generalized across Android, iOS, and other apps in the monorepo. The tool must be free and open source.
Chosen stack: Appium + WebdriverIO + Cucumber — exactly the same stack already used in treetracker-wallet-app/apps/bdd/. This is fully free/open source, already familiar to the team, supports Android and iOS, and follows the exact existing pattern in the monorepo.
Create treetracker-android/e2e/ following the identical structure as treetracker-wallet-app/apps/bdd/. The two test suites use the same tooling and can be extended to any other app in the monorepo by adding a new capabilities.ts entry.
treetracker-android/e2e/
├── package.json # WebdriverIO + Cucumber deps (mirrors apps/bdd)
├── tsconfig.json # TypeScript config (mirrors apps/bdd)
├── wdio.base.conf.ts # Shared config: Cucumber, reporters, video, hooks
├── wdio.mobile.conf.ts # Appium service + Android/iOS capabilities
├── .env.example # APK_PATH, DEVICE_NAME env vars
├── utils/
│ ├── capabilities.ts # Android (org.greenstand) + iOS capabilities
│ └── artifacts.ts # Video/screenshot helpers (copy from apps/bdd)
├── features/
│ ├── 01_splash_to_dashboard.feature # Returning user: Splash → Dashboard
│ ├── 02_signup_flow.feature # First-run: Splash → Language → Sign Up
│ ├── 03_capture_setup.feature # Dashboard → User Select → Wallet → Session Note
│ ├── 04_tree_capture.feature # Camera → Review → Height
│ ├── 05_settings.feature # Settings → Profile → Delete Profile
│ ├── 06_messages.feature # Messages → Chat / Survey / Announcement
│ ├── 07_org.feature # Org Picker → Add Org
│ └── step-definitions/
│ └── android.steps.ts # Appium step implementations
└── test-artifacts/ # Auto-generated: reports, videos, screenshots
utils/capabilities.ts — overrides the com.gtw.app wallet entries with:
export const CAPABILITY_ANDROID = [{
platformName: "Android",
"appium:deviceName": process.env.DEVICE_NAME || "emulator-5554",
"appium:app": process.env.APK_PATH, // path to app-debug.apk
"appium:automationName": "UiAutomator2",
"appium:appPackage": "org.greenstand.android.TreeTracker",
"appium:appActivity": "org.greenstand.android.TreeTracker.activities.TreeTrackerActivity",
"appium:autoGrantPermissions": true, // grants CAMERA + LOCATION automatically
"appium:noReset": false,
"appium:newCommandTimeout": 240,
}];"appium:autoGrantPermissions": true handles the location + camera permission dialogs that SplashScreen triggers — no extra steps needed.
@native
Feature: Splash to Dashboard (returning user)
Scenario: Returning user lands on Dashboard
Given the app is launched with an existing user
Then I should see "Trees Synced"
And I should see the upload button@native
Feature: First-run signup flow
Scenario: New user completes signup
Given the app is launched fresh
Then I should see the language selection screen
When I tap "English"
And I tap "Continue"
Then I should see the signup screen
And I should see "Phone Number"@native
Feature: Settings and profile management
Scenario: Navigate to settings and view profile
Given the app is launched with an existing user
When I navigate to Settings
Then I should see "Settings"
When I tap "Profiles"
Then I should see "Select Profile"
When I tap on the first profile
Then I should see "Delete Profile"Steps use UIAutomator text selectors — no testTag changes needed in the app source:
import { Given, When, Then } from "@wdio/cucumber-framework";
import { $, browser, expect } from "@wdio/globals";
// Find elements by visible text (works on all Compose screens)
const byText = (text: string) => $(`android=new UiSelector().text("${text}")`);
const byTextContains = (text: string) => $(`android=new UiSelector().textContains("${text}")`);
Given("the app is launched with an existing user", async () => {
// App launched via Appium capabilities; wait for Dashboard
await (await byText("Trees Synced")).waitForDisplayed({ timeout: 20000 });
});
Given("the app is launched fresh", async () => {
await browser.terminateApp("org.greenstand.android.TreeTracker");
await browser.activateApp("org.greenstand.android.TreeTracker");
await (await byTextContains("Language")).waitForDisplayed({ timeout: 15000 });
});
When("I tap {string}", async (text: string) => {
const el = await byText(text);
await el.waitForDisplayed({ timeout: 10000 });
await el.click();
});
Then("I should see {string}", async (text: string) => {
const el = await byText(text);
await expect(el).toBeDisplayed();
});{
"name": "@greenstand/android-e2e",
"scripts": {
"test:android": "cross-env PLATFORM=android wdio run ./wdio.mobile.conf.ts",
"test:ios": "cross-env PLATFORM=ios wdio run ./wdio.mobile.conf.ts",
"report": "node scripts/generate-cucumber-report.js"
},
"devDependencies": {
"@wdio/cli": "^9.18.4",
"@wdio/cucumber-framework": "^9.18.0",
"@wdio/globals": "^9.17.0",
"@wdio/local-runner": "^9.18.4",
"@wdio/spec-reporter": "^9.18.0",
"cross-env": "^10.1.0",
"dotenv": "^17.2.3",
"ts-node": "^10.9.2",
"typescript": "^5.9.2",
"wdio-cucumberjs-json-reporter": "^6.0.1",
"wdio-video-reporter": "^6.1.1",
"wdio-wait-for": "^3.1.0"
}
}- Create
treetracker-android/e2e/directory structure - Copy + adapt
wdio.base.conf.tsandwdio.mobile.conf.tsfromapps/bdd/— only change:cucumberOpts.requirepath andcapabilities.tsimport - Write
utils/capabilities.tswith treetracker-android's appPackage + appActivity - Read screen source files to identify landmark text for each of the 25 screens — used in
assertVisiblestep assertions - Write 7 feature files covering all screens grouped by user flow
- Write
android.steps.tswith UIAutomator text selectors (no app source changes needed) - Run: build APK → start emulator → run tests
To add another app (e.g. wallet native, iOS):
- Add a new entry to
utils/capabilities.tswith the new appPackage/bundleId - Add feature files under
features/for that app's screens - Tag them
@wallet-nativeor similar, filter in config
This scales to any app in the monorepo with zero new tooling.
# One-time global installs (free, open source)
npm install -g appium
appium driver install uiautomator2 # Android
appium driver install xcuitest # iOS (Mac only)
# Build the APK
cd treetracker-android
./gradlew assembleDebug
# APK at: app/build/outputs/apk/debug/app-debug.apk
# Start emulator (Android Studio AVD or `emulator -avd <name>`)
# Run all tests
cd e2e
APK_PATH=../app/build/outputs/apk/debug/app-debug.apk npm run test:android
# Run a single feature
APK_PATH=... npx wdio run ./wdio.mobile.conf.ts --spec features/01_splash_to_dashboard.featureTest results: HTML report + MP4 video per scenario in test-artifacts/.