How to Test Order Tracking on Android (Complete Guide)

Order tracking is the final touchpoint between a purchase and the customer’s perception of reliability. When the tracking screen shows stale data, fails to refresh, or leaks personal information, user

April 25, 2026 · 15 min read · How-To Guides

Why Order Tracking Deserves Focused Testing

Order tracking is the final touchpoint between a purchase and the customer’s perception of reliability. When the tracking screen shows stale data, fails to refresh, or leaks personal information, users abandon the app, leave negative reviews, and trigger chargebacks. In production, the most common failures stem from three sources: mismatched state between the UI and the backend order service, unhandled network transitions (Wi‑Fi to cellular, airplane mode, captive portals), and side‑effects from background optimizations that kill services responsible for polling or push‑notification handling. Because the tracking flow often re‑uses components from other parts of the app (navigation drawer, deep links, share sheets), a defect here can cascade into login, cart, or checkout modules. A dedicated test effort therefore protects revenue, brand trust, and compliance with accessibility and privacy regulations.

Deconstructing the Typical Order‑Tracking Flow

Before writing tests, map the concrete steps the app performs from the moment a user opens the tracking screen to the point where the order is marked delivered or cancelled. A typical Android implementation includes:

StepUI ActionBackend CallExpected State Change
1User taps “My Orders” → selects an orderGET /orders/{orderId} (summary)Order status = PROCESSING, UI shows progress bar
2Pull‑to‑refresh or automatic polling intervalGET /orders/{orderId}/tracking (details)UI populates map, timeline, ETA
3System receives FCM push with new locationNo UI action (background service)Service updates local DB, triggers UI refresh via LiveData
4User taps “View on map”Opens external map intent with lat/longNo backend call, UI launches Maps
5User shares tracking infoShare intent with text/URLNo backend call, UI shows chooser
6Order status changes to DELIVERED or CANCELLED (backend push)FCM or WebSocket pushUI updates status badge, shows “Delivered” banner
7User swipes away or rotates deviceConfiguration changeViewModel survives, UI recreates with same data

Each step is a potential failure point: missing or delayed network call, a race condition between UI and ViewModel, or a misuse of Android lifecycle callbacks. The matrix below enumerates the test scenarios that cover these points.

Comprehensive Test Matrix for Order Tracking

CategoryIDDescriptionTest DataExpected ResultAutomation Feasibility
Happy PathHP1Open tracking for an order in PROCESSING state, see correct timeline and mapOrderId = 123, status = PROCESSING, two tracking pointsUI shows two points, ETA calculated, refresh spinner stops after 5 sHigh (Espresso)
Happy PathHP2Pull‑to‑refresh triggers new backend fetch and updates UISame order, backend returns new locationUI updates map marker, timestamp changesHigh
Error PathEP1Backend returns 500 on tracking requestMock server returns 500UI shows error toast, retry button appears, no crashMedium (Espresso + MockWebServer)
Error PathEP2No network (airplane mode) when opening trackingAirplane mode enabledUI shows offline banner, cached data if available, no ANRHigh
Error PathEP3FCM push payload malformed (missing lat/lng)Push with empty dataUI does not crash, logs warning, retains last known locationMedium
Edge CaseED1User opens tracking while another order is being cancelled in backgroundTwo orders: A = PROCESSING, B = CANCELLINGTracking for A remains unaffected, UI does not show B’s cancellation bannerLow (requires UI Automator)
Edge CaseED2Rapid orientation changes during pollingRotate device 5 times within 10 sViewModel retains polling subscription, no duplicate network callsMedium
Edge CaseED3Deep link to tracking screen from external app (e.g., email)Intent with action=VIEW, data=susatest://track/123App launches directly to tracking UI, bypasses login if session valid, else shows login then trackingHigh
Edge CaseED4User leaves app in background for >30 min, then returns; order status changed to DELIVEREDBackground → Foreground after status pushUI updates to DELIVERED without manual refreshMedium
AccessibilityAC1TalkBack reads all tracking elements in logical orderEnable TalkBackFocus moves from order ID → status → timeline → map → share button, each announces correct contentMedium (UI Automator + AccessibilityTest)
AccessibilityAC2Font scaling up to 200 % does not clip textSet fontScale=2.0 in developer optionsAll text views resize, layout remains scrollable, no overlapLow (requires UI Automator)
AccessibilityAC3Color contrast meets WCAG AA for status badgesUse red/green/badgesContrast ratio ≥ 4.5:1 for normal text, ≥ 3:1 for large textLow (automated with AndroidX Test Core)
Security/PrivacySP1Order ID not exposed in logs or clipboard when sharingShare tracking infoClipboard contains only human‑readable summary, no raw UUID or tokenMedium (Espresso + adb logcat)
Security/PrivacySP2Deep link validates token before showing dataInvalid token in URLApp redirects to login or shows “Unauthorized” error, does not display order dataMedium
Security/PrivacySP3No personal data (name, address) appears in screenshots taken via adb shell screencapTake screenshot after sharingScreenshot redacted or app flags secure flag on relevant viewsLow (requires FLAG_SECURE)
PerformancePF1Initial load time < 2 s on mid‑tier device (Snapdragon 660)Cold start, no network cacheUI shows skeleton then data within limitMedium (BenchmarkTest)
PerformancePF2Polling interval respects battery‑optimization modesEnable Battery SaverPolling interval increases to ≥ 5 min or switches to push‑onlyLow (requires JobScheduler inspection)

*Automation Feasibility* is a rough guide: High means the scenario can be covered with Espresso/JUnit; Medium often needs MockWebServer, BroadcastReceiver tricks, or UI Automator; Low may require device‑farm testing or manual validation.

Manual Testing Approach – Step‑by‑Step

1. Environment Preparation

2. Happy Path Validation

  1. Launch the app, log in with a test account that has at least one order in PROCESSING.
  2. Navigate to My Orders → select the order → verify the tracking screen loads within 2 s.
  3. Confirm the UI shows: order ID, current status badge, a map with at least one marker, and a chronological timeline.
  4. Pull‑to‑release; observe a spinner, then new map marker if the mock backend returned a fresh location.
  5. Rotate the device twice; ensure data persists and no duplicate network calls appear in logcat (look for GET /tracking).
  6. Share the tracking info via the share button; verify the chooser appears and the shared text contains the tracking URL but no raw JWT.

3. Error Path Injection

4. Edge‑Case Exploration

5. Accessibility Checks

6. Security & Privacy Verification

7. Performance Spot‑Check

8. Post‑Test Cleanup

Automated Testing Strategies

Unit & ViewModel Tests

Validate the logic that transforms raw tracking responses into UI state without Android framework overhead.


// TrackingViewModelTest.kt
class TrackingViewModelTest {

    private lateinit var viewModel: TrackingViewModel
    private lateinit var dispatcher: Dispatchers.Unconfined
    private lateinit var mockRepository: TrackingRepository

    @Before
    fun setUp() {
        dispatcher = Dispatchers.Unconfined
        mockRepository = mockk()
        viewModel = TrackingViewModel(mockRepository)
    }

    @Test
    fun `polling updates state when new location arrives`() = runTest {
        // given
        val initial = TrackingResponse(status = "PROCESSING", points = listOf(Point(0,0)))
        val update   = TrackingResponse(status = "PROCESSING", points = listOf(Point(0,0), Point(10,10)))
        coEvery { mockRepository.fetchTracking(any()) } returns initial then returns update

        // when
        viewModel.startPolling()
        advanceUntilIdle()          // first fetch
        advanceTimeBy(30_seconds)   // simulate polling interval
        advanceUntilIdle()          // second fetch

        // then
        assertEquals(update.points, viewModel.trackingState.value?.points)
    }
}

*Why this matters*: The ViewModel owns the polling lifecycle; a bug here would cause duplicate requests or stale UI, which is hard to catch with pure UI tests.

Instrumented UI Tests with Espresso

Espresso shines for deterministic user actions and assertions on the UI thread.


// TrackingHappyPathTest.kt
@RunWith(AndroidJUnit4::class)
class TrackingHappyPathTest {

    @get:Rule
    val activityRule = ActivityScenarioRule(MainActivity::class.java)

    @Before
    fun setUp() {
        // MockWebServer enqueues a successful tracking response
        MockWebServerHelper.enqueueTrackingSuccess()
    }

    @Test
    fun trackingScreen_showsMapAndTimeline() {
        // navigate to order
        onView(withId(R.id.myOrders)).perform(click())
        onView(withText("Order #123")).perform(click())

        // assert UI elements
        onView(withId(R.id.orderStatusBadge)).check(matches(withText("Processing")))
        onView(withId(R.id.mapView)).check(matches(isDisplayed()))
        onView(withId(R.id.timelineRecyclerView)).check(matches(hasMinimumChildCount(2)))

        // pull‑to‑refresh
        onView(withId(R.id.swipeRefresh)).perform(swipeDown())
        onView(withId(R.id.progressBar)).check(matches(isDisplayed()))
        onView(withId(R.id.progressBar)).check(matches(not(isDisplayed())))

        // share flow
        onView(withId(R.id.btnShare)).perform(click())
        onView(withText("Copy link")).perform(click())
        // clipboard assertion via AndroidX Test Core
        val clipboard = getInstrumentation().targetContext.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
        val clip = clipboard.primaryClip
        assertNotNull(clip)
        assertTrue(clip.getItemAt(0).text.toString().contains("susatest://track/123"))
    }
}

*Key techniques*:

UI Automator for Cross‑App Scenarios

When testing deep links, share intents, or behavior under battery optimizations, UI Automator can interact with the system UI.


// DeepLinkTest.java
@RunWith(AndroidJUnit4.class)
public class DeepLinkTest {

    @Before
    public void setUp() {
        UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        device.pressHome();
    }

    @Test
    public void openingTrackingViaDeepLinkShowsCorrectOrder() {
        // Assume user is logged in via a helper method
        LoginHelper.loginViaUi(device);

        // Fire the implicit intent
        Intent intent = new Intent(Intent.ACTION_VIEW,
                Uri.parse("susatest://track/777"));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        InstrumentationRegistry.getInstrumentation()
                .getTargetContext()
                .startActivity(intent);

        // Wait for the tracking activity to appear
        UiObject orderId = new UiObject(new UiSelector()
                .resourceId("com.example.app:id/orderIdText")
                .textContains("777"));
        assertTrue(orderId.waitForExists(5000));

        // Verify status badge
        UiObject status = new UiObject(new UiSelector()
                .resourceId("com.example.app:id/orderStatusBadge"));
        assertEquals("Processing", status.getText().toString());
    }
}

*When to use*: Validating that the app correctly handles intents originating from other apps (email, messaging) and that the back stack behaves as expected.

Playwright for WebView‑Based Tracking

If part of the tracking screen is rendered in a WebView (e.g., a third‑party map widget), Playwright can drive the web context alongside native Espresso.


// WebViewTrackingTest.kt
class WebViewTrackingTest {

    private lateinit var playwright: Playwright
    private lateinit var browser: Browser
    private lateinit var context: BrowserContext
    private lateinit var page: Page

    @Before
    fun beforeAll() {
        playwright = Playwright.create()
        browser = playwright.chromium.launch()
        context = browser.newContext()
        page = context.newPage()
    }

    @Test
    fun webViewMap_loadsCorrectTiles() {
        // Launch the app and navigate to tracking screen via ActivityScenario
        ActivityScenario.launch(TrackingActivity::class.java)

        // Attach to the WebView (assuming its web contents have a known URL pattern)
        val webView = onView(withId(R.id.mapWebView))
                .check(matches(isDisplayed()))
        // Use AndroidX Espresso Web to get the web contents URL
        // Then load that URL in Playwright context
        page.goto("https://maps.example.com/track/123")
        expect(page.locator(".map-tile")).toHaveCountGreaterThan(0)
    }

    @After
    fun afterAll() {
        page.close()
        context.close()
        browser.close()
        playwright.close()
    }
}

*Advantage*: You can assert on web‑specific features (map tiles, WebGL errors) while still testing the native wrapper.

CI Integration

Production‑Only Edge Cases

Even the most exhaustive lab matrix can miss issues that appear only under real‑world conditions. Below are patterns that repeatedly surface in post‑release monitoring and how to reproduce them in a controlled fashion.

PhenomenonRoot CauseReproduction Technique
Stale tracking after network reconnectThe app’s ConnectivityManager listener fails to restart polling after a captive‑portal Wi‑Fi login.Use adb shell cmd connectivity set-mobile-data-enabled false, then connect to a Wi‑Fi hotspot that requires a browser login; after login, re‑enable mobile data and observe whether polling resumes.
Duplicate notificationsTwo FCM registration tokens are generated after a silent app update, causing the backend to send duplicate push messages.Simulate a token reset via FirebaseInstanceId.getInstance().deleteInstanceId() in a test build, then send two pushes with the same payload; verify the UI shows only one update.
Battery‑optimization kills polling JobSchedulerOn Xiaomi/OnePlus devices, aggressive Doze mode postpones jobs beyond the expected interval, leading to missed updates.Enable Battery Optimization for the app (Settings → Battery → App power management), force a Doze idle state with adb shell dumpsys deviceidle force-idle, then send a push and check latency.
Map tile exhaustionThe third‑party map SDK hits its daily quota, returning blank tiles that the app treats as successful loads.Mock the map SDK’s tile endpoint to return HTTP 429 after a certain number of requests; ensure the app displays an error overlay rather than a blank map.
Order ID leakage via recent‑tasks thumbnailAndroid’s recent‑tasks view captures a screenshot of the tracking screen, exposing the order ID to any app with GET_TASKS permission (pre‑Android 9).Launch the app, navigate to tracking, press Home, open recent apps via adb shell am start -a android.settings.RECENT_APPS_SETTINGS, and verify the thumbnail is blurred or the activity is marked android:excludeFromRecents="true" or uses setSecure(true).
Locale‑switch mid‑flow causing layout breakUser changes system language while the tracking screen is visible; hard‑coded strings cause overlapping views.Use adb shell setprop persist.sys.language es && adb shell setprop persist.sys.country ES && adb shell stop && adb shell start to switch language, then verify the UI still renders correctly.
Push payload larger than binder limitBackend sends a tracking update with a huge polyline (>1 MB) causing TransactionTooLargeException when delivered via FCM.Construct a FCM payload with a 2 MB data field and send it via adb shell cmd notification post ...; ensure the app catches the exception and logs gracefully instead of crashing.
Background location throttling on Android 12+The app requests foreground location but relies on a background service for updates; Android 12 imposes stricter limits, causing stale data.Target API 31, deny background location permission (APP_OP_FINE_LOCATION in background), then simulate a location change and confirm the app falls back to push‑only updates.

Mitigation Strategies to Encode in Tests

Checklist for Order‑Tracking Validation

✅ ItemDescription
Happy‑path loadTracking screen appears < 2 s, shows correct status, map, timeline.
Pull‑to‑refreshTriggers new network call, updates UI, shows spinner then hides it.
Error handling500/timeout → error toast + retry; no crash, no ANR.
Network lossOffline banner appears, cached data shown if available, retry restores data.
Push updateFCM payload updates map/timeline without manual refresh.
Orientation changeViewModel survives, no duplicate network calls, UI state intact.
Deep linkLaunches directly to tracking (or login → tracking) with correct order.
Share flowChooser appears, shared text contains order link, no raw tokens exposed.
AccessibilityTalkBack reads all elements in order; font scale 200 % retains layout; contrast ≥ 4.5:1.
SecurityNo order ID or token in logs/clippayload; deep link validates token; recent‑tasks thumbnail secured.
Battery optimizationPolling interval respects Battery Saver; push‑only fallback works.
LocalizationLanguage switch mid‑flow does not break layout; strings update correctly.
PerformanceInitial load, refresh, and push latency all under defined thresholds on mid‑tier device.
RegressionAutomated test suite (Espresso + UI Automator + Playwright) passes on every CI run.

Run this checklist before each release candidate; any item marked triggers a blocker until resolved.

Closing Takeaways

Order tracking is more than a static screen—it is a live contract between the app, the backend, and the user’s expectations. Failures in this flow directly affect trust, conversion, and revenue, yet they are notoriously hard to catch because they involve asynchronous networking, background pushes, lifecycle transitions, and system‑level interactions like deep links or accessibility services.

A disciplined testing strategy combines three layers:

  1. Unit/ViewModel tests that guarantee the business logic around polling, deduplication, and state transitions is correct.
  2. Instrumented UI tests (Espresso, UI Automator, Playwright) that verify the user‑visible behavior under controlled network mocks, configuration changes, and intents.
  3. Exploratory, persona‑driven sessions—whether performed manually or via an autonomous agent like SUSA—that surface the rare, production‑only glitches such as captive‑portal reconnect bugs, map‑quota exhaustion, or recent‑tasks leakage.

When these layers are reinforced by a concrete test matrix, a repeatable manual checklist, and CI‑gated automation, teams can ship order‑tracking updates with confidence that the UI will stay in sync with the backend, that users with diverse needs will receive the same information, and that personal data will remain protected throughout the flow.

Invest the time now to automate the happy and error paths, reserve exploratory runs for the edge cases that only appear in the wild, and let the results guide your next round of refactoring—your users (and your support tickets) will thank you.

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