How to Automate Location Services Testing (Step-by-Step)
How to Automate Location Services Testing (Step-by-Step) is a practical guide for developers and QA engineers who need reliable, repeatable validation of GPS, geofencing, and mock‑location features in
How to Automate Location Services Testing (Step-by-Step) is a practical guide for developers and QA engineers who need reliable, repeatable validation of GPS, geofencing, and mock‑location features in mobile and web applications. Location‑based behavior often hides behind device‑specific quirks, network latency, and permission flows that are tedious to verify manually. Automating these checks reduces regression risk, uncovers edge cases that only appear under certain coordinate sets or accuracy levels, and frees testers to focus on exploratory work. The following sections walk through a complete, end‑to‑end process: deciding when automation pays off, picking a framework, crafting maintainable tests, handling waits and flakiness, managing data setup and teardown, integrating with CI, and surfacing actionable reports. Each step includes concrete code snippets, a test matrix, and a tool‑comparison table to help you make informed decisions.
How to Automate Location Services Testing (Step-by-Step): Setting Up the Environment
Before writing any test, you need a stable, controllable environment that can simulate location changes without relying on physical movement. For Android, the Android Emulator provides the geo fix command via ADB, while iOS simulators expose xcrun simctl location. Web tests can override the Geolocation API using browser‑level mocks or devtools protocol commands.
Installing Required Tools
- Android: Android Studio SDK platform‑tools (adb), emulator system images with Google Play services, and optionally Genymotion for richer sensor simulation.
- iOS: Xcode command‑line tools,
xcrun simctl, and a device‑specific simulator runtime. - Web: Chrome or Firefox with ChromeDriver/GeckoDriver, plus the Selenium or Playwright bindings for your language.
- Language bindings: Choose Java, JavaScript/TypeScript, Python, or C# based on your team’s existing test stack. Install the appropriate package manager artifacts (Maven, npm, pip, dotnet).
Configuring Mock Location Permissions
On Android, the test harness must grant the ACCESS_MOCK_LOCATION permission to the test APK or to the emulator’s shell. Add the following to your test setup script:
adb root
adb shell appops set <package_name> android:mock_location allow
adb shell pm grant <package_name> android.permission.ACCESS_MOCK_LOCATION
On iOS, enable the “Allow Location Simulation” toggle in the simulator’s Debug menu, or use:
xcrun simctl location booted set <latitude> <longitude>
For web, launch Chrome with the --disable-features=Geolocation flag and then inject a mock via the DevTools Protocol:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
# Override geolocation for the context
context.grant_permissions(['geolocation'], origin='https://maps.example.com')
context.set_geolocation({'latitude': 40.7128, 'longitude': -74.0060, 'accuracy': 10})
page = context.new_page()
page.goto('https://maps.example.com')
# … test steps …
browser.close()
Verifying the Setup
Run a quick sanity check that the mock location is being consumed:
- Android:
adb shell dumpsys locationshows the last mock location. - iOS: In the simulator, open Maps and confirm the blue dot appears at the injected coordinates.
- Web: Open the console and execute
navigator.geolocation.getCurrentInfo => console.log(position); the logged values should match your mock.
If the values differ, re‑check permission grants and ensure no other process is overriding the location provider (e.g., a background location service).
With a controllable location source in place, you can now decide which aspects of location services merit automation.
How to Automate Location Services Testing (Step-by-Step): When Automation Pays Off
Not every location‑related scenario needs a script. Manual exploratory testing remains valuable for assessing UI feel, accessibility of permission dialogs, and ad‑hoc edge cases. Automation shines when you need repeatable validation across multiple coordinate sets, accuracy levels, or when the feature is part of a critical user flow such as ride‑hailing, asset tracking, or geofenced notifications.
Criteria for Automation
| Criterion | Description | Automation Benefit |
|---|---|---|
| Repeatability | Same coordinate sequence must be exercised many times (e.g., regression after each build). | Eliminates human error, ensures exact same inputs. |
| Combinatorial Explosion | Testing latitude/longitude pairs, altitude, accuracy, speed, and heading creates dozens of combos. | Scripts can iterate over data files quickly. |
| Time‑Sensitive Triggers | Geofence entry/exit depends on dwell time or speed thresholds. | Precise control over timestamps and movement simulation. |
| Cross‑Platform Consistency | Same feature must behave identically on Android, iOS, and web. | Centralized test logic reduces divergence. |
| Failure‑Prone Permissions | Runtime permission dialogs can flake if timing shifts. | Scripts can grant/revoke permissions programmatically, removing UI timing variance. |
If your feature meets at least three of the above criteria, invest in automated location tests. Otherwise, a lightweight manual checklist may suffice.
Example: Ride‑Hailing Pickup Flow
Consider a ride‑hail app where the user taps “Set Pickup”, the map centers on the current location, and a “Confirm” button appears only when the accuracy radius is < 50 m. Manual testing would require walking around or using a joystick emulator; automation can inject a series of locations with varying accuracy to confirm the button appears/disappears at the right thresholds.
A test matrix for this feature might look like:
| Test ID | Latitude | Longitude | Accuracy (m) | Expected Button State | Notes |
|---|---|---|---|---|---|
| P1 | 37.7749 | -122.4194 | 10 | Enabled | High accuracy, inside geofence |
| P2 | 37.7749 | -122.4194 | 100 | Disabled | Low accuracy, should block confirm |
| P3 | 37.7749 | -122.4194 | 50 | Enabled | Boundary case |
| P4 | 37.8000 | -122.5000 | 10 | Disabled | Outside service area |
Automating this matrix lets you run the same checks on every commit, catching regressions where the accuracy check is accidentally removed or the UI bound to the wrong view model property.
How to Automate Location Services Testing (Step-by‑Step): Choosing a Framework
Selecting a test framework influences language choice, device control capabilities, and ease of integrating location mocks. The most common options for mobile are Appium (Android/iOS) and platform‑specific tools like Espresso/XCUITest. For web, Playwright and Selenium dominate. Below is a comparison focused on location‑services support.
Tool‑Comparison Table
| Feature | Appium | Espresso (Android) | XCUITest (iOS) | Playwright | Selenium WebDriver |
|---|---|---|---|---|---|
| Language Support | Java, JS, Python, C#, Ruby | Java/Kotlin | Swift/Obj‑C | JS/TS, Python, Java, C# | Java, JS, Python, C#, Ruby |
| Location Mocking | adb geo fix or mobile: setLocation | LocationManager test doubles via Dependency Injection | CLLocationManager mock via XCTest | browser.context.set_geolocation or page.evaluate override | execute_script to override navigator.geolocation |
| Geofence Simulation | Send latitude/longitude updates repeatedly; can emulate speed via adb shell svc location | Requires custom test double or Firebase Test Lab location spoofing | Use CLLocation test double; limited to simulator | Can fire setInterval to update location; easy to simulate heading/speed | Same as Playwright via execute_script |
| Permission Handling | mobile: permission command; can grant/revoke at runtime | grantRuntimePermission via UiAutomator | XCUITest addLocationAuthorization | browserContext.grantPermissions | execute_script to navigate to chrome://settings/content/location |
| Cross‑Platform | Yes (single codebase) | No (Android only) | No (iOS only) | Yes (Chromium, Firefox, WebKit) | Yes (multiple browsers) |
| Speed & Stability | Moderate (JSON wire protocol overhead) | High (in‑process) | High (in‑process) | High (devtools protocol) | Moderate to high (depends on browser) |
| Learning Curve | Medium (need to understand server) | Low for Android devs | Low for iOS devs | Low‑Medium (API is concise) | Medium (Selenium Grid complexity) |
| Best For | Teams needing one script for both platforms, hybrid apps | Pure Android native apps with extensive unit‑testable location logic | Pure iOS native apps | Web apps, PWAs, hybrid web views | Legacy web projects, Selenium‑grid shops |
If your project already uses Appium for functional UI tests, extending it with location commands is the lowest‑friction path. For greenfield native projects, writing location‑specific unit tests with Espresso/XCUITest and then layering UI validation offers the best speed and hybrid apps, and WebView(
Now let's see how to integrate location mocking into each framework.
#### Appium Example (Java)
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.AndroidDriver;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class LocationTest {
private AppiumDriver driver;
@BeforeEach
public void setUp() throws Exception {
URL serverUrl = new URL("http://localhost:4723/wd/hub");
Map<String, Object> caps = new HashMap<>();
caps.put("platformName", "Android");
caps.put("deviceName", "Pixel_4_API_33");
caps.put("appPackage", "com.example.myapp");
caps.put("appActivity", ".MainActivity");
driver = new AndroidDriver(serverUrl, caps);
}
@Test
public void testGeofenceEntry() {
// Set initial location outside geofence
driver.setLocation(37.7700, -122.4200, 50);
// Perform actions that should NOT trigger geofence
assertFalse(isGeofenceNotificationVisible());
// Move inside geofence
driver.setLocation(37.7749, -122.4194, 10);
// Wait for possible notification (use explicit wait)
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(d -> isGeofenceNotificationVisible());
assertTrue(isGeofenceNotificationVisible());
}
private boolean isGeofenceNotificationVisible() {
// Implement based on your app's UI (e.g., toast, dialog, status bar icon)
return driver.findElements(By.id("geofence_alert")).size() > 0;
}
@AfterEach
public void tearDown() {
if (driver != null) driver.quit();
}
}
#### Playwright Example (TypeScript)
import { test, expect } from '@playwright/test';
test('geofence triggers notification', async ({ page }) => {
await page.goto('https://myapp.com/map');
// Mock initial location outside the geofence
await page.context().setGeolocation({ latitude: 37.7700, longitude: -122.4200, accuracy: 50 });
await page.context().grantPermissions(['geolocation']);
await page.waitForTimeout(500); // allow time
const outsideNotify = page.locator('#geofalert');
await outsideNotifyNotifyfBeVisible()).toNot(beBeVisible());
// Move inside geofence
await page.context().setGeolocation({ latitude: 37.7749, longitude: -122.4194, accuracy: 10 });
await page.waitForTimeout(500);
await expect(page.locator('#geofence-alert')).toBeVisible();
});
These snippets illustrate the core actions: setting coordinates, granting permissions, and asserting UI reactions. Adjust the locators and wait strategies to match your application's specifics.
How to Automate Location Services Testing (Step-by‑Step): Writing Stable/Maintainable Tests
Stability hinges on isolating location logic from flaky UI timing, using deterministic data, and encapsulating repetitive steps in reusable helpers. Below are patterns that keep your test suite readable and robust.
1. Abstract Location Actions
Create a utility class or module that wraps platform‑specific location commands. This isolates changes if you switch from Appium to Espresso, for example.
// LocationHelper.java (Appium + Java)
public class LocationHelper {
private final AppiumDriver driver;
public LocationHelper(AppiumDriver driver) {
this.driver = driver;
}
public void setLocation(double lat, double lng, float accuracy) {
driver.setLocation(lat, lng, accuracy);
}
public void grantLocationPermission() {
// Android specific
driver.executeScript("mobile: permission", ImmutableMap.of(
"action", "grant",
"appId", driver.getCapabilities().getCapability("appPackage"),
"permission", "android.permission.ACCESS_FINE_LOCATION"
));
}
public void revokeLocationPermission() {
driver.executeScript("mobile: permission", ImmutableMap.of(
"action", "revoke",
"appId", driver.getCapabilities().getCapability("appPackage"),
"permission", "android.permission.ACCESS_FINE_LOCATION"
));
}
}
Use this helper in every test: new LocationHelper(driver).setLocation(...);
2. Parameterize Test Data
Store coordinate sets, accuracy values, and expected outcomes in external files (CSV, JSON, YAML). This enables non‑technical stakeholders to edit scenarios without touching code.
// geofence_scenarios.json
[
{"id":"S1","lat":37.7749,"lng":-122.4194,"acc":10,"expected":"inside"},
{"id":"S2","lat":37.7749,"lng":-122.4194,"acc":100,"expected":"outside"},
{"id":"S3","lat":37.8000,"lng":-122.5000,"acc":10,"expected":"outside"}
]
In JUnit 5 with @ParameterizedTest and a custom ArgumentProvider, you can feed each entry to a single test method.
@ParameterizedTest
@ArgumentSource(GeofenceArgumentsProvider.class)
void testGeofenceScenario(String id, double lat, double lng, float acc, String expected) {
locationHelper.setLocation(lat, lng, acc);
locationHelper.grantLocationPermission();
// perform the action that should trigger geofence check
boolean inside = isInsideGeofence(); // your UI check
Assertions.assertEquals(expected.equals("inside"), inside,
() -> "Scenario " + id + " failed");
}
3. Use Explicit Waits, Not Sleeps
Location updates may take a few hundred milliseconds to propagate through the OS stack. Replace Thread.sleep with condition‑based waits.
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
wait.until(d -> {
Location loc = ((AndroidDriver) d).getLocation();
return loc.getLatitude() == lat && loc.getLongitude() == lng;
});
4. Encapsulate UI Assertions
If your app shows a toast, status bar icon, write a small method that returns a boolean. This makes test method bodies short: private boolean isGeofenceToastVisible() {
return driver.findElements(By.id("toast_geofence")).stream()
.anyMatch(e -> e.isDisplayed());
}
### 5. Handle Permission Flakiness
On Android 12+ the permission dialog may appear after a location request. Instead of assuming it’s already granted, actively grant/revoke in the test setup. For iOS, use `XCUITest`’s `addLocationAuthorization` or `simctl` commands in a `beforeEach` hook.
### 6. Keep Tests Idempotent
Each test should start from a known location state (e.g., set to a default “null” location like 0,0) and end with the same state. This prevents cross‑test contamination when running in parallel.
Applying these patterns yields a test suite that remains green across refactors, device OS updates, and framework upgrades.
## How to Automate Location Services Testing (Step-by‑Step): Locator Strategy
Location‑based features often rely on map widgets (Google Maps SDK, Mapbox, Apple MapKit) or custom canvas renders. Locators that depend on pixel coordinates or dynamically generated IDs are brittle. Adopt strategies that survive UI theme changes, localization, and map tile updates.
### 1. Prefer Accessibility IDs
Both Android and iOS expose accessibility labels that are less likely to change than view IDs. For a map pin, set an accessibility label like `"pickup_pin"` in the code and locate it via:
* **Android (Espresso)**: `onView(withContentDescription("pickup_pin"))`
* **iOS (XCUITest)**: `app.buttons["pickup_pin"]` (if you set the button’s accessibilityIdentifier)
### 2. Use Data‑Test Attributes for Web
Add `data-testid` attributes to map‑related elements (markers, info windows, buttons). Example:
Then locate with Playwright: `page.locator('[data-testid="map-marker"]')`.
### 3. Leverage Map‑Specific APIs
Some map SDKs expose methods to query rendered features. For instance, Mapbox GL JS offers `map.queryRenderedFeatures`. You can call this via `page.evaluate` to get a list of features at a given screen point, then assert properties like `feature.properties.type === 'pickup'`.
const features = await page.evaluate(() => {
return map.queryRenderedFeatures([x, y], { layers: ['symbol-layer'] });
});
expect(features.some(f => f.properties.id === 'pickup-1')).toBe(true);
This approach sidesteps DOM fragility altogether.
### 4. Avoid Hard‑Coded Pixel Coordinates
If you must interact with the map by coordinate, compute the point relative to the map container bounding box:
// Java
int centerX = mapView.getWidth() / 2;
intmap via tap coordinates, compute them from the map container’s dimensions rather than using magic numbers. Example in Appium:
Dimension size = driver.manage().window().getSize();
int startX = size.width / 2;
int startY = size.height / 2;
new TouchAction(driver)
.press(PointOption.point(startX, startY))
.waitAction(WaitOptions.waitOptions(Duration.ofMillis(200)))
.moveTo(PointOption.point(endX, endY))
.release()
.perform();
5. Test Map Tile Loading Separately
Map tile network failures can cause false negatives. Include a checkpoint that verifies at least one tile has loaded before interacting with markers. For Android, you can check MapView.isMyLocationEnabled() or observe that onTileLoaded callback fires a known number of times.
By anchoring locators to semantic attributes or SDK query methods, your tests survive UI redesigns and remain readable.
How to Automate Location Services Testing (Step-by‑Step): Handling Waits and Flakiness
Even with solid locators, location services introduce timing variables: GPS fix latency, network‑based location provider delays, and animation durations for map transitions. Mitigate flakiness with a combination of explicit waits, retry logic, and observability.
1. Explicit Waits for Location‑Dependent UI
Instead of waiting a fixed time, wait for a condition that directly reflects the location state. Example using Appium’s WebDriverWait in Java:
new WebDriverWait(driver, Duration.ofSeconds(10))
.until(d) {
driver.getLocation() != null &&
d -> {
Location loc = ((AndroidDriver) d).getLocation();
return Math.abs(loc.getLatitude() - targetLat) < 0.0001 &&
Math.abs(loc.getLongitude() < 0.0001;
);
2. Polling every 500 ms until condition true.
return Math.abs(loc.getLatitude() - expectedLat) < 0.00001 &&
Math.abs(loc.getLongitude() - expectedLng) < 0.00001;
});
### 2. Retry Wrapper for Assertions
Sometimes the UI updates a fraction of a second after the location change. Wrap assertions in a retry loop with exponential back‑off.
public static void assertWithRetry(BooleanSupplier condition, String message, int maxAttempts) {
int attempt = 0;
while (attempt < maxAttempts) {
if (condition.getAsBoolean()) return;
try { Thread.sleep(200 * (1 << attempt)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
attempt++;
}
throw new AssertionError(message + " after " + maxAttempts + " attempts");
}
// Usage
assertWithRetry(() -> isPickupButtonEnabled(),
"Pickup button never enabled after location set", 5);
### 3. Use Event‑Based Triggers
If your app emits events (e.g., via LiveData, RxJava, Combine) when location updates arrive, expose a test hook or observe a test‑only observable. For Android, you can add a `MutableLiveData<Location>` that the production code observes; in tests, you set its value directly.
// Production code
locationRepository.getLocationLiveData().observe(lifecycleOwner, loc -> {
updateUI(loc);
});
// Test code
@Test
void testLocationUpdate() {
MutableLiveData
// Inject testLive into the repository via dependency injection
repository.setLocationLiveDataForTest(testLive);
// UI observes the LiveData
activityScenario.onActivity(activity -> {
// set location
testLive.setValue(new Location("gps") {{
setLatitude(37.7749);
setLongitude(-122.4194);
setAccuracy(10);
}});
});
// assert UI changed
onView(withId(R.id.pickup_button)).check(matches(isEnabled()));
}
This eliminates reliance on timing altogether.
### 4. Capture Logs and Metrics
Enable location‑related logging (`adb logcat | grep LocationManager`) and assert that expected log lines appear within a timeout. This gives an early signal if the location provider never received the mock.
LogCollector logCollector = new LogCollector(driver);
logCollector.startCapture();
locationHelper.setLocation(lat, lng, acc);
logCollector.awaitLineContains("Mock location set", Duration.ofSeconds(5));
### 5. Stabilize Map Animations
Map SDKs often animate camera changes. Disable animations in test builds or wait for the `onCameraIdle` callback (Android) or `map.isCameraMoving` (iOS) to become false.
// Android: turn off animations in test-only build: map.getUiSettings().setScrollGesturesEnabled(false);
By combining condition‑based waits, retry mechanisms, and event hooks, you reduce false positives caused by the inherent asynchrony of location services.
## How to Automate Location Services Testing (Step-by‑Step): Data Setup and Teardown
Location tests often depend on device state such as mocked GPS, network mode, or battery level. Proper setup and teardown guarantee that each test runs in isolation and that the emulator/simulator returns to a clean baseline.
### 1. Reset Location Provider
After each test, clear any mock location and restore the provider to its default state.
@AfterEach
public void resetLocation() {
// Clear mock location
driver.executeScript("mobile: geoFix", ImmutableMap.of(
"latitude", 0,
"longitude", 0,
"accuracy", 0
));
// Optionally disable mock location permission to avoid leakage
driver.executeScript("mobile: permission", ImmutableMap.of(
"action", "revoke",
"appId", driver.getCapabilities().getCapability("appPackage"),
"permission", "android.permission.ACCESS_MOCK_LOCATION"
));
}
For iOS simulators:
xcrun simctl location booted unset
### 2. Clear Geofences
If your app registers geofences via the OS, explicitly remove them to prevent cross‑test interference.
// Android example using LocationManager via reflection or a test-only method
public void clearGeofences() {
// Assuming you exposed a test-only method in your LocationRepository
repository.clearAllGeofencesForTest();
}
If no test hook exists, you can invoke the OS command to delete all geofences for the package:
adb shell cmd appops set
adb shell pm clear
### 3. Manage Network Conditions
Location may rely on Wi‑Fi or cellular for assisted GPS. Use the emulator’s network controls to simulate different conditions:
# Emulate 3G slow network
adb emu network speed 3g
# Return to full speed
adb emu network speed full
Wrap these commands in setup/teardown methods if you need to test under degraded connectivity.
### 4. Battery and Power State
Some location APIs change behavior under low‑power mode. You can set battery level via:
adb shell dumpsys battery set level 20
adb shell dumpsys battery set status 2 // 2 = charging
Reset to default after each test.
### 5. teardown Hook Summary
A typical JUnit `@AfterEach` might look like:
@AfterEach
public void tearDown() {
// 1. Reset mock location
locationHelper.setLocation(0, 0, 0);
// 2. Revoke mock location permission (optional)
locationHelper.revokeLocationPermission();
// 3. Clear app-specific geofences
repository.clearGeofencesForTest();
// 4. Reset network to full speed
try { Runtime.getRuntime().exec("adb emu network speed full"); } catch (IOException ignored) {}
// 5. Reset battery to 100% unplugged
try { Runtime.getRuntime().exec("adb shell dumpsys battery set level 100"); } catch (IOException ignored) {}
// 6. Close any lingering dialogs
dismissSystemDialogs();
}
Implementing deterministic setup and teardown eliminates “it works on my machine” flakiness and makes CI runs reliable.
## How to Automate Location Services Testing (Step‑by‑Step): Running in CI
Integrating location tests into a continuous‑integration pipeline ensures that regressions are caught early. The key challenges are provisioning emulators/simulators with location‑mocking capabilities, managing test execution time, and collecting artifacts for debugging.
### 1. Choose the Right Execution Environment
* **Linux‑based agents** (most CI services) can run Android Emulator via the `android-emulator-runner` script.
* **macOS agents** are required for iOS simulators and Xcode‑based tests.
* For web, any agent with Chrome/Firefox installed works.
If you use a hosted service like GitHub Actions, GitLab CI, or Azure Pipelines, select the appropriate runner image (`ubuntu-latest` for Android, `macos-latest` for iOS).
### 2. Emulator/Simulator Provisioning Script
Create a reusable script that starts an emulator with Google Play services, grants mock location permission, and sets a default location.
# start_android_emulator.sh
#!/usr/bin/env bash
EMULATOR_NAME="Pixel_4_API_33"
# Create if not exists
if ! avdmanager list avd | grep -q "$EMULATOR_NAME"; then
echo "no" | avdmanager create avd -n "$EMULATOR_NAME" -k "system-images;android-33;google_apis_playstore;x86_64"
fi
# Start emulator in background
emulator -avd "$EMULATOR_NAME" -no-window -no-audio -gpu swiftshader_indirect &
EMULATOR_PID=$!
# Wait for boot
adb wait-for-device
adb shell svc wifi enable
# Grant mock location to the app under test (replace with your package)
adb shell pm grant com.example.myapp android.permission.ACCESS_MOCK_LOCATION
# Set initial dummy location
adb shell geo fix 0 0
# Export emulator PID for later cleanup
echo $EMULATOR_PID > emulator.pid
A similar script for iOS:
# start_ios_simulator.sh
#!/usr/bin/env bash
SIM_NAME="iPhone_14"
# Boot a specific device type and OS version
xcrun simctl boot "$SIM_NAME"
# Wait for boot
while [[ $(xcrun simctl list | grep -i Booted" ]]; do sleep 1
done
# Grant location: xcrun simctl list devices | grep "$SIM_NAME.*Booted" ]]; do
sleep 1
done
done
# Grant location simulation permission (requires enabling in Debug menu automatically via simctl)
xcrun simctl spawn booted launchctl setenv SIMULATOR_CHAMBER_UID Enabled
# Set initial location
xcrun simctl location booted set 0 0
### 3. Parallel Execution
Location tests are often I/O‑bound (waiting for emulator response). Running multiple emulator instances in parallel can reduce total pipeline time. Use Docker containers or separate VM agents, each with its own emulator port (`emulator -port 5556`, `-port 5558`, etc.). Ensure each test picks up its assigned port via an environment variable.
### 4. Artifact Collection
When a test fails, capture:
* **Logcat** (`adb logcat > logcat.txt`)
* **Simulator console output** (`xcrun simctl spawn booted log --console`)
* **Screenshots** (`adb shell screencap -p /sdcard/fail.png && adb pull /sdcard/fail.png .`)
* **Video** (emulator `-record video.webm`)
Configure your CI to upload these as build artifacts or attach them to the test report.
### 5. Example GitHub Actions Workflow (Android)
name: Location Tests
on:
push:
branches: [ main ]
pull_request:
jobs:
android-location:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free