How to Test Delivery Tracking on Android (Complete Guide)

Delivery tracking is the visible heartbeat of any e‑commerce, food‑order, or logistics app. Users open the tracking screen to see where their package is, estimate arrival time, and decide whether they

April 18, 2026 · 17 min read · How-To Guides

Introduction: Why Delivery Tracking Is Critical

Delivery tracking is the visible heartbeat of any e‑commerce, food‑order, or logistics app. Users open the tracking screen to see where their package is, estimate arrival time, and decide whether they need to be home or redirect the shipment. When this screen fails—shows stale data, crashes, or hides essential controls—trust erodes instantly, leading to abandoned carts, negative reviews, and increased support load.

Testing tracking on Android is not a trivial UI check. The screen pulls data from multiple sources: a backend API, location services, push notifications, and sometimes a local cache. It must handle network flakiness, GPS drift, permission denials, and a variety of device configurations (screen sizes, Android versions, OEM customizations). A single missed edge case can produce a silent failure that only appears after the app has been in the wild for weeks.

This guide walks you through a complete testing strategy: from a concrete test matrix that covers happy paths, error paths, accessibility, and security, to manual procedures, automated scripts with Espresso/UI Automator and Appium, and finally how autonomous, persona‑driven exploration can surface bugs that scripted tests never consider.

Core Components of Delivery Tracking UI

Understanding what the tracking screen actually does helps you design meaningful tests. Typical Android tracking screens contain the following elements:

UI ElementTypical ImplementationWhat It Represents
Toolbar with back buttonandroidx.appcompat.widget.ToolbarNavigation back to order list
Order summary cardCardView with text and imageOrder ID, items, total price
Map fragmentcom.google.android.gms.maps.MapFragment or MapViewLive vehicle or package location
Status timelineRecyclerView with custom item layoutChronological steps (picked, in transit, out for delivery, delivered)
ETA badgeTextView with background colorEstimated time of arrival
Action buttonsMaterialButton (Call driver, Share tracking, Reschedule)User‑initiated interactions
Progress indicatorCircularProgressIndicator or horizontal barLoading state while fetching updates
Error bannerLinearLayout with TextView shown on failureNetwork or server error messages

Each component can be a source of bugs. The map may fail to initialize if Google Play Services is out of date. The timeline may recycle views incorrectly, causing stale status text. The ETA badge may not update when the backend pushes a new estimate via Firebase Cloud Messaging.

When you write tests, treat each element as a testable unit and also consider their interactions (e.g., tapping the map should open a full‑screen map view).

Common Production Failures

Before diving into the test matrix, it helps to know what actually breaks in the field. The following failure patterns appear repeatedly across delivery‑tracking apps on Android:

Knowing these patterns helps you prioritize test cases and craft realistic edge‑case scenarios.

Comprehensive Test Matrix

Below is a detailed matrix that groups test ideas by dimension (functional, error, edge, accessibility, security) and by scenario (happy path, error path, production‑only). Each row includes a short description, the expected result, and the suggested verification method.

IDDimensionScenarioDescriptionExpected ResultVerification
T1FunctionalHappy pathUser opens tracking after order placement; map shows current location, timeline shows “Order placed”, ETA shows realistic time.All UI elements load within 2 s, no errors in Logcat.Manual inspection + Espresso assertion on visibility and text.
T2FunctionalHappy pathUser taps “Share tracking”; system share sheet appears with pre‑filled text containing order ID and link.Share intent fires, correct data in extras.Espresso intents().intended(...) with hasExtra.
T3FunctionalError pathBackend returns 500 for location update; app shows error banner and retains last known data.Error banner visible, map does not crash, timeline unchanged.Mock server with WireMock, assert banner visibility.
T4FunctionalError pathUser denies location permission; app prompts rationale and disables map.Permission rationale dialog appears, map shows static placeholder, no crash.Use adb shell pm revoke then launch activity, check dialog.
T5EdgeNetwork switchStart on Wi‑Fi, simulate loss of connectivity, then restore on cellular.App retries request, ETA updates after reconnection, no spinner stuck >10 s.Use adb shell cmd connectivity to toggle, measure time with SystemClock.
T6EdgeDevice rotationRotate device while timeline is mid‑animation.Timeline state preserved, no duplicate items, map recenters correctly.Espresso perform(rotate()), assert item count unchanged.
T7EdgeLow memoryRun app in background with memory‑intensive app, then return.Tracking screen restores from saved instance state, no crash.Use adb shell am kill after allocating memory via stress-ng.
T8AccessibilityWCAG 2.1 AAAll icons have contentDescription, color contrast ≥4.5:1, touch target ≥48dp.TalkBack reads each element correctly, no overlapping touch areas.Run Accessibility Scan Test (Espresso) or Android Studio Accessibility Scanner.
T9AccessibilityFont scalingUser sets system font size to 200 %.All text scales, layouts do not truncate or overflow.Change font size via Settings, assert text size via getTextSize().
T10SecurityData leakageNo order ID, address, or token appears in Logcat or Crashlytics breadcrumbs.Sensitive strings absent from logs.Use adb logcat while performing actions, grep for patterns.
T11SecurityIntent safety“Call driver” uses implicit intent with Intent.ACTION_DIAL and resolves only if PackageManager.queryIntentActivities returns non‑empty.Button disabled or shows toast on devices without telephony.Query package manager, assert button enabled state.
T12Production‑onlyGPS driftSimulate GPS jitter using a mock location provider that jumps ±500 m every 2 s.Map shows smooth movement, ETA recalculates without jumping backwards.Use adb shell cmd location set-test-provider and feed mock NMEA.
T13Production‑onlyBattery optimizationDevice puts app in background restriction after 30 min of inactivity.Tracking updates resume when user returns to foreground, no missed updates.Use adb shell cmd appops set RUN_IN_BACKGROUND ignore, then background the app.
T14Production‑onlyOEM dark mode overrideForce dark mode via developer options on a device that ignores app theme.UI adapts, text remains readable, no hard‑coded colors break contrast.Enable force dark, verify contrast with Accessibility Scanner.
T15Production‑onlyPush notification delayServer sends ETA update via FCM with 10‑second delay; app processes it even if in doze mode.ETA badge updates after delay, no missed update.Use adb shell cmd jobscheduler run -f to simulate.

This matrix gives you a concrete starting point. You can expand each ID into a full test case with pre‑conditions, test data, and cleanup steps.

Manual Testing Procedure (Step‑by‑Step)

Even with automation, a disciplined manual pass catches nuance that scripts can miss, especially around gestures, sensor behavior, and OEM quirks. Follow this checklist for each build you intend to release to a internal test channel or production.

  1. Environment preparation
  1. Happy‑path walkthrough
  1. Error‑path injection
  1. Location and sensor simulation
  1. Interaction tests
  1. Accessibility check
  1. Security sniff
  1. OEM and theme validation
  1. Battery‑optimization test
  1. Final sign‑off

Following these steps manually once per release candidate gives you confidence that the most obvious regressions are caught before they reach users.

Automated Testing with Android Espresso & UI Automator

Espresso excels at verifying UI state within your app’s process, while UI Automator reaches across system boundaries (e.g., permission dialogs, share sheets). Combining both gives you fast, reliable tests that run on every pull request.

Setting up the test module

Add the following dependencies to your app‑level build.gradle:


dependencies {
    androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1"
    androidTestImplementation "androidx.test.espresso:espresso-intents:3.5.1"
    androidTestImplementation "androidx.test.uiautomator:uiautomator:2.3.0"
    androidTestImplementation "androidx.test:runner:1.5.2"
    androidTestImplementation "androidx.test:rules:1.5.0"
}

Create a test class under src/androidTest/java/com/example/tracking/TrackingScreenTest.kt.

Happy‑path Espresso test


@RunWith(AndroidJUnit4::class)
class TrackingScreenTest {

    @get:Rule
    val intentsRule = IntentsTestRule(TrackingActivity::class.java)

    @Test
    fun `tracking loads and shows correct data`() {
        // Assume a test order is pre‑loaded via a dependency‑injected fake repository
        onView(withId(R.id.toolbar_title))
            .check(matches(withText(containsString("ORDER #12345"))))

        // Map fragment should be visible
        onView(withId(R.id.map_fragment))
            .check(matches(isDisplayed()))

        // Timeline first item
        onView(withId(R.id.recycler_timeline))
            .check(matches(isDisplayed()))
        onView(withId(R.id.recycler_timeline))
            .perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(0))
        onView(withId(R.id.item_status_text))
            .check(matches(withText(containsString("Order placed"))))

        // ETA badge
        onView(withId(R.id.eta_badge))
            .check(matches(withText(matches(Pattern.compile("\\d{1,2}:\\d{2}\\s*(AM|PM)")))))
    }
}

Error‑path with mocked server

Use WireMock or MockWebServer to return a 500 for the location endpoint.


@Test
fun `location error shows banner and does not crash`() {
    // enqueue a 500 response
    mockWebServer.enqueue(MockResponse()
        .setResponseCode(500)
        .setBody(""))

    onView(withId(R.id.refresh_button))
        .perform(click())

    onView(withId(R.id.error_banner))
        .check(matches(isDisplayed()))
    onView(withId(R.id.map_fragment))
        .check(matches(isDisplayed())) // map should not crash
}

UI Automator for system dialogs

When testing the location‑permission denial flow, you need to interact with the system permission dialog.


@Test
fun `location permission denied shows rationale`() {
    // Revoke permission via ADB before launching
    val uid = Runtime.getRuntime()
        .exec("adb shell pm get-compat-mode com.example.tracking")
        .inputStream
        .readAllBytes()
        .toString(Charsets.UTF_8.name())

    launchActivity<TrackingActivity>()

    val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
    // Wait for the permission dialog
    val allowButton = uiDevice.findObject(
        UiSelector().text("Allow").className(android.widget.Button::class.java.name))
    assertFalse(allowButton.exists()) // should not exist because we revoked

    val rationale = uiDevice.findObject(
        UiSelector().textContains("We need your location").className(android.widget.TextView::class.java.name))
    assertTrue(rationale.exists())
}

Sharing intent verification

Espresso‑Intents lets you assert that the correct share intent was fired.


@Test
fun `share tracking sends correct data`() {
    intending(toPackage("com.android.sharedreams")).respondWith(
        InstrumentationResult.ActivityResult(Activity.RESULT_OK, null))

    onView(withId(R.id.btn_share))
        .perform(click())

    intended(allOf(
        hasAction(Intent.ACTION_SEND),
        hasExtra(Intent.EXTRA_TEXT, startsWith("Check out my order #12345"))
    ))
}

Running the suite

Execute locally with Gradle:


./gradlew connectedAndroidTest   # runs on all attached devices/emulators

For CI, configure Firebase Test Lab or GitHub Actions to spin up a matrix of devices (different API levels, screen sizes, OEM images).

Leveraging Appium for Cross‑Device Validation

While Espresso/UI Automator give you speed and deep‑in‑app assertions, Appium enables you to write the same tests in a language‑agnostic way and run them against real devices or emulators without recompiling the test APK. This is valuable for validating behavior across OEM firmware builds that may alter system dialogs or default apps.

Appium setup

  1. Install Node.js and the Appium server:

npm install -g appium
appium driver install uiautomator2
  1. Start the server:

appium --allow-insecure=chromedriver_autodownload
  1. Write a Java (or JavaScript) test using the Appium Java client.

import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;
import org.junit.*;
import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.*;

public class TrackingAppiumTest {

    private AndroidDriver driver;
    private WebDriverWait wait;

    @Before
    public void setUp() throws Exception {
        UiAutomator2Options options = new UiAutomator2Options()
                .setPlatformName("Android")
                .setAutomationName("UiAutomator2")
                .setAppPackage("com.example.tracking")
                .setAppActivity("com.example.tracking.TrackingActivity")
                .setNoReset(true);
        driver = new AndroidDriver(new URL("http://localhost:4723"), options);
        wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    @Test
    public void testEtaUpdatesAfterLocationChange() {
        // Wait for tracking screen to load
        WebElement eta = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("eta_badge")));
        String initialEta = eta.getText();

        // Send a mock location via ADB (Appium can execute shell commands)
        driver.executeScript("mobile: shell", ImmutableMap.of(
                "command", "geo",
                "args", List.of("-122.4194", "37.7749")));

        // Wait for ETA to change (up to 15 seconds)
        WebElement newEta = wait.until((WebDriver d) -> {
            String text = driver.findElement(By.id("eta_badge")).getText();
            return !text.equals(initialEta);
        });

        Assert.assertNotEquals(initialEta, newEta.getText());
    }

    @After
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Why Appium helps delivery‑tracking tests

Example: Testing the “Call driver” button on a tablet


@Test
public void callDriverButtonIsDisabledOnTablet() {
    boolean isPhone = driver.isDeviceLocked(); // placeholder; actual check via telephony manager
    WebElement callBtn = driver.findElement(By.id("btn_call_driver"));
    if (!isPhone) {
        Assert.assertTrue(callBtn.getAttribute("enabled").equals("false"));
        // Optionally verify a toast
        WebElement toast = wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//android.widget.Toast")));
        Assert.assertTrue(toast.getText().contains("Calling not supported"));
    } else {
        Assert.assertTrue(callBtn.isEnabled());
    }
}

Appium tests are slower than Espresso (they traverse the UI hierarchy via the accessibility service), but they give you confidence that the app works on the actual hardware your customers use.

Accessibility and WCAG Checks for Tracking Screens

Accessibility is not a nice‑to‑have; it’s a legal requirement in many jurisdictions and directly impacts user satisfaction. Delivery‑tracking screens often contain custom views (map markers, timeline icons) that are easy to overlook.

Automated accessibility scanning with Espresso

Add the androidx.test.espresso:accessibility-checker dependency:


androidTestImplementation "androidx.test.espresso:accessibility-checker:3.5.1"

Then enable the checker in your test class:


@Before
fun enableAccessibilityChecks() {
    AccessibilityChecks.enable()
}

Running any Espresso test will now automatically perform a scan after each action and fail if violations are found (e.g., missing contentDescription, insufficient contrast).

Manual verification checklist

CheckHow to testPass criteria
ContentDescription on iconsEnable TalkBack, swipe to each timeline iconEach icon announces a meaningful phrase (e.g., “Out for delivery, truck icon”).
Color contrastUse Android Studio’s Accessibility Scanner or a contrast‑checking appText vs. background ratio ≥ 4.5:1 for normal text, ≥ 3:1 for large text.
Touch target sizeEnable “Show layout bounds” in Developer optionsEvery interactive element (buttons, map markers) occupies at least 48 dp × 48 dp.
Scalable fontsIncrease system font size to 200 %No text is clipped, layouts reflow or scroll as needed.
Screen reader navigation orderTalkBack linear navigationFocus moves logically from toolbar → order summary → map → timeline → action buttons.
Accessible error messagesTrigger a network failureError banner is announced and describes the problem and possible recovery steps.

Fixing common issues

Security and Privacy Considerations

Delivery tracking inevitably exposes personal data: order ID, customer name, delivery address, and sometimes payment token. A breach or inadvertent leak can lead to fraud, identity theft, or regulatory penalties (GDPR, CCPA).

Data‑in‑transit

Data‑at‑rest

Logging

Permissions

Testing for leaks

Use adb logcat combined with grep to search for known patterns:


adb logcat -v threadtime | grep -E "(order|address|token|password)"

If any line returns a match, investigate the logging call and replace it with a redacted version.

Dynamic analysis tools like MobSF or the Android Studio Data Safety section can also surface hard‑coded strings or insecure HTTP usage.

Edge Cases That Appear Only in Production

Even the most exhaustive test matrix can miss issues that only surface under real‑world user conditions. Below are several production‑only phenomena that have caused outages in delivery‑tracking apps, along with tactics to surface them earlier.

PhenomenonWhy it hides in labDetection strategy
GPS drift under urban canyonIndoor tests get a stable fix; outdoors, multipath causes jumps.Use a GPS spoofing app (e.g., Fake GPS Go) to inject jitter ±30 m every second while the app is foreground. Observe map smoothness and ETA stability.
Background location throttlingTest devices often have battery optimization disabled.Enable “Background location limits” (Settings → Location → Advanced → Background location limits) and verify that location updates still arrive via a foreground service.
Push notification doze delaysEmulators deliver FCM instantly.Force doze mode (adb shell dumpsys deviceidle force-idle) then send an FCM payload; measure latency between send and UI update.
Carrier‑specific SMS OTP interceptionSome carriers block or delay SMS; test environments use virtual numbers.Use a real SIM from a problematic carrier (identified via field‑monitoring) and simulate delayed delivery via a proxy that holds the SMS for 10‑15 s before forwarding.
OEM‑specific aggressive RAM managementTest devices have plenty of free RAM.Install a memory‑stress app (e.g., com.github.ksk0013.RamBooster) that constantly allocates and frees large chunks, then launch the tracking app and verify it recovers from a low‑memory kill without losing state.
Network handover stalls (Wi‑Fi → LTE → 5G)Lab networks are static.Use a network emulator like clumsy or tc on a Linux router to inject latency spikes and packet loss during handover, then check that the app retries with exponential backoff and does not show a perpetual spinner.
Multilingual layout breakageQA often tests only en‑US.Change device language to Arabic or Hebrew (RTL) and ensure the toolbar, timeline, and map controls mirror correctly.
Accessibility service interferenceTalkBack may be off in test builds.Enable TalkBack, Switch Access, or a third‑party accessibility service and confirm that all gestures (double‑tap, swipe) still work as expected.

To catch these earlier, incorporate production‑like test harnesses into your CI:

Autonomous, Persona‑Driven Exploration (Mentioning SUSA)

Scripted tests verify that the app behaves as you expect, but they cannot anticipate the myriad ways real users interact with a feature. Autonomous testing platforms that simulate diverse user personas can surface bugs hidden in edge‑case flows, unexpected gesture combinations, or accessibility‑driven navigation patterns that a scripted tester would never think to write.

One such platform is SUSA, an autonomous QA agent that you point at an APK or a web URL. After you upload the tracking‑app build, SUSA explores the application without any pre‑written scripts, using a set of built‑in personas:

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