Mobile E2E Testing
The React Native application's Android E2E suite uses Maestro, an emulator, and a Docker-backed API environment. The same ordered suite runs automatically in every GitLab merge-request pipeline.
Prerequisites
Install and configure:
- Docker Desktop or Docker Engine with Docker Compose
- Node.js and the mobile repository's Yarn dependencies
- Java 17
- Android SDK platform tools and a running Android emulator
- Maestro CLI
- Access to the Coldtivate container images in the GitLab registry
Use a GitLab personal, project, or deploy token with read_registry permission. Do not use an account password or commit the token.
Configure the Local App
From the mobile-app-react-native repository, create the ignored local environment file:
Set its service URLs for the Android emulator:
BASE_API_BASE_URL=http://10.0.2.2:8080/base_api/
AIR_PROD_BASE_URL=http://10.0.2.2:8080/air_api/
IMPACT_BACKUP_BASE_URL=http://10.0.2.2:8080/impact_api/
FARMER_IMPACT_BASE_URL=http://10.0.2.2:8080/farmer_api/
10.0.2.2 is the Android emulator's route to the development host. .env.e2e.local is ignored by Git and may contain developer-specific values; tracked environment files must retain placeholders and CI-safe defaults.
The E2E build uses deterministic location data and does not require a Mapbox token.
Start and Reset the Backend
From the mobile repository root, run:
The setup script stops the existing E2E services and recreates the base database from the persistent e2e_seed database template. That template is initialized from e2e_seed.sql the first time it is created. The script then applies migrations and roles and starts the Docker services. Rerun it before a clean suite to restore deterministic test data. If e2e_seed.sql changes, remove or recreate the existing template before expecting those changes to appear. The database is exposed on host port 54321 by default.
Build and Install the Android App
Build the E2E APK with the local environment file and Java 17:
cd android
E2E_ENVFILE=.env.e2e.local \
SENTRY_DISABLE_AUTO_UPLOAD=true \
./gradlew app:assembleE2e
cd ..
E2E builds use Android debug signing and do not require the production keystore password. Disabling Sentry upload prevents a local build from requiring a Sentry release token.
Start an emulator, verify that ADB sees it, and install the APK:
The -d flag permits installation over an app with a higher version code. If installation remains inconsistent, uninstall the existing app package and reinstall; uninstalling clears that app's local data.
Run the Tests
Run the complete ordered suite:
Use the runner instead of maestro test .maestro. Some Maestro versions can discover zero nested flows for that directory while returning a successful-looking run.
Run one flow while developing or diagnosing a failure:
The app uses 10.0.2.2 because it runs inside the emulator. Maestro JavaScript helpers run on the development host, so they use localhost for direct API requests.
Capture Local Reports and Debug Output
To mirror the CI output locally:
mkdir -p e2e-artifacts/debug e2e-artifacts/tests
BASE_API_BASE_URL=http://localhost:8080/base_api \
.maestro/run-all.sh \
--format JUNIT \
--output e2e-artifacts/maestro-junit.xml \
--debug-output e2e-artifacts/debug \
--test-output-dir e2e-artifacts/tests \
--flatten-debug-output
Without explicit output paths, Maestro keeps local test results under ~/.maestro/tests. The repository's e2e-artifacts directory is ignored by Git.
Merge-request Pipeline
For each merge_request_event, GitLab:
- Builds the Android E2E APK with Sentry source-map upload disabled.
- Starts an Android emulator and freshly seeded Docker services.
- Executes every flow through
.maestro/run-all.sh. - Publishes the JUnit file in GitLab's test summary.
- Retains Maestro output, screenshots, Android logcat, and Docker logs for one week, including failed jobs.
The test job has allow_failure: false. The GitLab project must also enable Pipelines must succeed in its merge checks for an E2E failure to prevent merging.
Troubleshooting
Registry login is unauthorized
Use a non-expired GitLab token with read_registry scope. With two-factor authentication, an account password cannot authenticate Docker. Verify that the token's user can access the backend image project.
Sentry returns 401 Invalid token
Set SENTRY_DISABLE_AUTO_UPLOAD=true for local E2E builds. Source-map publication belongs to release builds and is not required to execute the suite.
The app cannot reach an API
Check that the app's local environment uses 10.0.2.2:8080, the host-side Maestro variable uses localhost:8080, and the Docker gateway port is exposed. Rerun ./docker/e2e/setup.sh, then inspect the same Compose project used by the setup script:
docker-compose \
--project-name=coldtivate-e2e \
--project-directory=docker/e2e \
--env-file=docker/e2e/.env.e2e \
ps
An element is not found
Inspect the failing run's screenshot and UI hierarchy in the Maestro debug output. Prefer a semantic application-owned testID over visible copy or identifiers inherited from a component library. Replace fixed-duration waits with assertions on visibility or enabled state where possible.
A flow passes alone but fails in the suite
Reset the backend with ./docker/e2e/setup.sh, then rerun the ordered suite. Check whether the flow assumes data created by another flow, reuses a fixed unique value, or leaves application state behind. Flows should either create their prerequisites explicitly or rely only on the known seed and documented ordering.
CI fails but no test is shown
Open the job's e2e-artifacts archive. It includes Maestro debug/test output, adb-logcat.txt, docker-compose-ps.txt, and docker-compose.log. If the JUnit file is missing, inspect the job trace for a failure before Maestro started.