How to Test Cart Management on Android (Complete Guide)
Cart management is the bridge between browsing and purchase. When users add items, adjust quantities, apply coupons, or proceed to checkout, any friction directly translates into abandoned baskets and
Motivation: Why Cart Management Deserves Focused Testing
Cart management is the bridge between browsing and purchase. When users add items, adjust quantities, apply coupons, or proceed to checkout, any friction directly translates into abandoned baskets and lost revenue. In Android apps, the cart lives in a mix of local storage, network calls, and UI layers that must stay consistent across configuration changes, background interruptions, and varied device capabilities. A single missed null‑check or race condition can cause crashes, incorrect totals, or silent data loss that only surfaces under real‑world load. Because the cart touches product data, pricing logic, payment integrations, and analytics, defects here have a cascading impact on trust, compliance, and brand perception.
Testing the cart therefore needs to go beyond “does the button work?”. It must validate state persistence, error handling, accessibility, and security under the same conditions users experience in the wild. The following guide walks through a complete strategy—from manual exploration to automated suites and autonomous persona‑driven testing—so you can catch the bugs that slip through scripted checks.
---
Core Concepts of Cart Management on Android
Before designing tests, clarify what the cart actually does in your architecture.
| Component | Typical Responsibility | Common Implementation |
|---|---|---|
| Data Model | Holds item IDs, quantities, selected variants, applied discounts | Kotlin data class or Protobuf persisted via Room, SharedPreferences, or a singleton |
| Persistence Layer | Survives process death, configuration changes, and app upgrades | Room database with @Entity for CartItem, or EncryptedSharedPreferences for token‑sensitive fields |
| UI Layer | Displays list, totals, action buttons; reacts to user input | RecyclerView with DiffUtil, Material components (Chip, Button, TextInputLayout) |
| Network Sync | Sends cart to backend for validation, price recomputation, or saved‑for‑later | Retrofit coroutines, WorkManager for background sync, or GraphQL mutations |
| Coupon / Promotion Engine | Adjusts line‑item totals, applies cart‑wide rules | Rule engine (Drools‑like) or remote config fetched via Firebase Remote Config |
| Checkout Initiator | Packages cart data, launches payment flow, clears or archives cart | Calls Payment SDK, then triggers CartRepository.clear() on success |
Understanding these pieces helps you decide where to inject faults (e.g., corrupt Room rows, network latency, missing coupon data) and what assertions to make (e.g., total equals sum of line items after tax).
---
Common Production Failures in Cart Management
Even with unit tests, certain defects only appear when the app runs on real devices under stress. Below is a non‑exhaustive list of failure modes observed in production Android carts, grouped by root cause.
| Failure Mode | Symptoms | Typical Trigger |
|---|---|---|
| State loss after rotation | Cart empties or shows stale items | UI recreated without restoring ViewModel state |
| Duplicate line items | Same product appears twice with combined quantity | Rapid “Add to cart” clicks without debouncing or idempotency guard |
| Incorrect totals after discount | Total shows pre‑discount amount | Discount applied before tax calculation or tax recalc omitted |
| Coupon code silently ignored | No discount, no error message | Backend returns 200 with empty promo object; client fails to parse |
| Cart exceeds max item limit | App crashes or shows overflow UI | Backend enforces limit; client never validates locally |
| Network timeout leaves cart in “pending” state | Spinner never disappears, user cannot proceed | Retrofit timeout not handled, UI not reset |
| Accessibility label missing | TalkBack reads “button” instead of “Remove item” | ContentDescription omitted or set to null in adapter |
| Sensitive data logged | Full SKU and price appear in Logcat | Debug logging left in production build |
| Race condition with background sync | Cart shows old price after promo ends | WorkManager updates cart while UI reads stale LiveData |
| Low‑memory kill kills Service | Cart cleared when user returns from recent apps | Cart stored only in a Service; no persistence fallback |
These issues often evade scripted tests because they depend on timing, device state, or specific user personas (e.g., a power user tapping rapidly). The next sections show how to catch them.
---
Test Matrix: Covering Happy Paths, Errors, Edges, Accessibility, and Security
Use the following matrix to plan test cases. Each row represents a scenario; columns indicate the test type (manual, automated UI, automated unit, accessibility, security). Mark ✓ where the scenario should be exercised; leave blank if not applicable.
| ID | Scenario | Manual | UI Automation (Espresso/UIAutomator) | Unit / Integration | Accessibility (TalkBack/Scanner) | Security / Privacy |
|---|---|---|---|---|---|---|
| H1 | Add single item, verify cart count updates | ✓ | ✓ | ✓ | ✓ | |
| H2 | Add multiple variants of same product, check line‑item aggregation | ✓ | ✓ | ✓ | ||
| H3 | Update quantity via +/− buttons, total recalculates correctly | ✓ | ✓ | ✓ | ✓ | |
| H4 | Apply valid coupon, discount reflected in line‑item and cart total | ✓ | ✓ | ✓ | ✓ | ✓ (ensure coupon code not logged) |
| H5 | Apply invalid coupon, shows error, cart unchanged | ✓ | ✓ | ✓ | ✓ | ✓ |
| H6 | Remove item, cart count decrements, total updates | ✓ | ✓ | ✓ | ✓ | |
| H7 | Clear cart via “Empty cart” button, UI shows empty state | ✓ | ✓ | ✓ | ✓ | |
| H8 | Rotate device while cart populated, cart persists exactly | ✓ | ✓ | ✓ | ✓ | |
| H9 | Leave app, return after 5 min, cart restored from persistence | ✓ | ✓ | ✓ | ||
| H10 | Add item, then lose network, cart still shows item, offline badge appears | ✓ | ✓ | ✓ | ||
| H11 | Network timeout during add‑to‑cart, retry mechanism works, no duplicate | ✓ | ✓ | ✓ | ✓ (no double charge) | |
| H12 | Attempt to add item exceeding max allowed quantity, validation error shown | ✓ | ✓ | ✓ | ✓ | |
| H13 | Add item, then quickly spam “Add” 10 times, only one line‑item added | ✓ | ✓ | ✓ | ||
| H14 | TalkBack navigation: each cart row announces product name, price, quantity, remove action | ✓ | ||||
| H15 | Color contrast of “Proceed to checkout” button meets WCAG AA (≥4.5:1) | ✓ | ||||
| H16 | Touch target size of cart action buttons ≥48 dp | ✓ | ||||
| H17 | Ensure no PII (user ID, email) appears in Logcat or Crashlytics cart‑related logs | ✓ | ||||
| H18 | Verify that cart data sent to backend is encrypted (TLS 1.2+) and no query‑string leakage | ✓ | ||||
| H19 | Attempt SQL injection via crafted product ID (if using raw query) – should be sanitized | ✓ | ✓ | |||
| H20 | Simulate low memory (via adb shell am kill or Android Studio profilers) – cart survives | ✓ | ✓ |
How to use the matrix
- For each feature (add, quantity change, coupon, etc.) start with the happy‑path rows (H1‑H4).
- Add error‑path rows (H5, H10‑H13) to validate defensive coding.
- Include accessibility rows (H14‑H16) early; they often uncover missing contentDescription or insufficient tap targets.
- Security rows (H17‑H19) are critical if the cart talks to a payment gateway or stores personal identifiers.
- Run the matrix on a variety of API levels (21‑34) and screen sizes (phones, foldables, tablets) to catch layout‑specific bugs.
---
Manual Testing Approach: Step‑by‑Step
Manual exploration remains valuable for discovering UX friction and edge cases that automated scripts may overlook. Follow this procedure on a physical device or emulator, noting observations in a test‑rail spreadsheet.
- Preparation
- Install the debug or release build via
adb install -r app.apk. - Clear app data:
adb shell pm clear com.example.app. - Enable Developer options → Show touches, Pointer location, and CPU usage overlay for visual feedback.
- Set up a proxy (e.g., Charles) to capture HTTP(S) traffic if you need to inspect payloads.
- Baseline Happy Path
- Navigate to a product list, tap Add to cart on the first item.
- Verify the cart badge increments to 1.
- Open the cart screen; confirm the item appears with correct thumbnail, name, price, quantity selector (default 1).
- Tap Proceed to checkout; ensure you reach the payment screen without errors.
- Quantity Manipulation
- Press + twice; quantity should become 3, total update accordingly.
- Press − once; quantity back to 2, total adjusts.
- Attempt to decrement below 1; the UI should disable the − button or show a toast “Minimum quantity is 1”.
- Coupon Flow
- Tap Apply coupon, enter a known valid code (e.g.,
SAVE10). - Observe discount applied to line item or cart total; verify the coupon label appears.
- Clear the field, enter an invalid code (
BADCODE); expect an error message and cart unchanged.
- Error Injection
- Turn off Wi‑Fi/mobile data, attempt to add an item.
- Confirm the app shows an offline indicator, but the item still appears in the cart (optimistic add).
- Restore network; watch for a sync indicator and eventual backend acknowledgment.
- Simulate a slow network using
tcor the network throttling in Android Studio Profiler (e.g., 150 ms latency, 50 kbps downlink). Observe spinner behavior and timeout handling.
- State Persistence Checks
- Rotate the device (or trigger configuration change via Developer options).
- Verify cart count, items, and totals remain identical.
- Press Home, open another app for ~30 seconds, then return to the app. Cart should be intact.
- Force‑stop the app (
adb shell am force-stop com.example.app), relaunch; cart should still be present if persisted to disk.
- Boundary & Stress Tests
- Add the same product 50 times rapidly (use a script or monkey‑style tapping).
- Confirm only one line‑item exists with quantity 50 (or the max allowed if a limit exists).
- If a max‑item limit is defined (e.g., 99), attempt to exceed it; verify a blocking message and cart unchanged.
- Accessibility Spot‑Check
- Enable TalkBack (
Settings → Accessibility → TalkBack). - Swipe through cart items; each should announce: “Product name, price, quantity, remove button, double tap to activate”.
- Verify that increasing/decreasing quantity via TalkBack triggers the appropriate action and announces the new total.
- Run the Accessibility Scanner app; note any failures (missing contentDescription, low contrast, touch target <48 dp).
- Security & Privacy Quick Security & Privacy
- Open Logcat (
adb logcat) and perform cart`) while interacting with the cart. - Search for any occurrence of SKU, price, or user ID in plain text.
- Confirm that network calls use
https://and that the certificate is valid (noNETWORK_CLEARTEXT_TRAFFICwarning). - If the app uses Firebase Crashlytics, verify that custom keys do not include cart contents.
- Post‑Test Cleanup
- Clear app data again to avoid contaminating subsequent test runs.
- Capture screenshots or screen recordings of any anomalous behavior for bug reports.
---
Automated Testing: Unit, Integration, and UI Layers
Automated checks give fast feedback on regressions. Structure them according to the Test Pyramid: many unit tests, fewer integration/UI tests, and a small set of end‑to‑end flows.
Unit Tests (Pure Logic)
Test the cart repository, view‑model, and discount calculator in isolation. Use JUnit5 and Mockito (or MockK) to stub data sources.
// CartViewModelTest.kt
@ExperimentalCoroutinesApi
class CartViewModelTest {
private lateinit var viewModel: CartViewModel
private lateinit var repository: FakeCartRepository
@Before
fun setUp() {
repository = FakeCartRepository()
viewModel = CartViewModel(repository)
}
@Test
fun `adding item updates live cart`() = runTest {
val product = Product(id = "p1", name = "T‑shirt", price = Money(1999))
viewModel.addToCart(product, 1)
val cart = viewModel.cartFlow.first() // collect first emission
assertEquals(1, cart.items.size)
assertEquals(product.id, cart.items.first().productId)
assertEquals(Money(1999 * 1), cart.total)
}
@Test
fun `applying valid coupon reduces total`() = runTest {
// pre‑populate cart
repository.addItem(ProductId("p1"), 2) // 2 × $19.99 = $39.98
viewModel.applyCoupon("SAVE10") // 10 % off
val cart = viewModel.cartFlow.first()
// Expected total after discount: $35.98 (rounded)
assertEquals(Money(3598), cart.total)
}
}
Key points:
- Use
runTestfromkotlinx-coroutines-testto handle suspending functions. - Assert on immutable data classes; avoid mutable state leakage.
- Test edge cases: negative quantity, duplicate add, coupon max‑use limits.
Integration Tests (Room + WorkManager)
Validate persistence and background synchronization. Use AndroidX Test with InstantTaskExecutorRule to execute LiveData synchronously.
// CartRepositoryIntegrationTest.kt
@RunWith(AndroidJUnit4::class)
class CartRepositoryIntegrationTest {
private lateinit var repository: CartRepository
private lateinit var context: Context
@Before
fun setUp() {
context = ApplicationProvider.getApplicationContext()
val db = Room.inMemoryDatabaseBuilder(
context, AppDatabase::class.java
).allowMainThreadQueries().build()
repository = CartRepository(db.cartDao())
}
@Test
fun `cart survives process kill`() {
val item = CartItem(productId = "p2", quantity = 3)
repository.addOrUpdate(item)
// Simulate process death by creating a new repo instance with same DB
val db2 = Room.inMemoryDatabaseBuilder(
context, AppDatabase::class.java
).allowMainThreadQueries().build()
val repo2 = CartRepository(db2.cartDao())
val cart = repo2.getCart().getOrAwaitValue()
assertEquals(3, cart.items.firstOrNull()?.quantity)
}
}
Here getOrAwaitValue() is an extension that blocks on LiveData for test purposes.
UI Tests (Espresso)
Exercise the full cart screen with realistic user gestures. Disable animations for flake‑free runs.
// CartUiTest.kt
@LargeTest
@RunWith(AndroidJUnit4::class)
class CartUiTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class)
@Before
fun disableAnimations() {
// Turn off window, transition, and animator durations
InstrumentationRegistry.getInstrumentation()
.uiAutomation
.executeShellCommand(
"settings put global window_animation_scale 0.0"
)
.executeShellCommand(
"settings put global transition_animation_scale 0.0"
)
.executeShellCommand(
"settings put global animator_duration_scale 0.0"
)
}
@Test
fun addItem_thenUpdateQuantity_reflectsInTotal() {
// Add first product from list
onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions.actionOnItemAtPosition(
0,
clickChildViewWithId(R.id.btn_add_to_cart)
))
// Verify badge
onView(withId(R.id.cart_badge))
.check(matches(withText("1")))
// Open cart
onView(withContentDescription("Open cart")).perform(click())
// Assert line item present
onView(withText("Premium T‑Shirt"))
.check(matches(isDisplayed()))
// Increase quantity twice
onView(withId(R.id.btn_quantity_plus))
.perform(click())
onView(withId(R.id.btn_quantity_plus))
.perform(click())
// Total should be 3 × unit price
onView(withId(R.id.tv_cart_total))
.check(matches(withText("$59.97"))) // assuming $19.99 each
}
@Test
fun applyInvalidCoupon_showsError() {
// Pre‑add an item
onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions.actionOnItemAtPosition(
0,
clickChildViewWithId(R.id.btn_add_to_cart)
))
onView(withContentDescription("Open cart")).perform(click())
onView(withId(R.id.btn_apply_coupon)).perform(click())
onView(withId(R.id.input_coupon)).perform(replaceText("NOTVALID"), closeSoftKeyboard())
onView(withId(R.id.btn_submit_coupon)).perform(click())
onView(withText("Coupon not found"))
.check(matches(isDisplayed()))
// Cart total unchanged
onView(withId(R.id.tv_cart_total))
.check(matches(withText("$19.99")))
}
}
Tips for stable Espresso tests:
- Use
IdlingResourceto wait for network or WorkManager completion. - Prefer
withContentDescriptionover text when the UI is localized. - Turn off animations as shown; otherwise, flaky timeouts appear.
UIAutomator for Cross‑App Scenarios
When you need to test interactions that leave your app (e.g., switching to a payment gateway app), UIAutomator works across process boundaries.
// CartCrossAppTest.java
@RunWith(AndroidJUnit4.class)
public class CartCrossAppTest {
private static final String LAUNCHER_PACKAGE = "com.android.launcher3";
@Test
public void proceedToCheckout_launchesPaymentApp() {
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Launch the app
Context ctx = InstrumentationRegistry.getInstrumentation().getTargetContext();
Intent intent = ctx.getPackageManager()
.getLaunchIntentForPackage("com.example.app");
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
ctx.startActivity(intent);
// Wait for main activity
UiObject2 main = device.wait(Until.findObject(By.res("com.example.app", "main_activity")), 5000);
assertNotNull(main);
// Add item to cart (same steps as Espresso but via UIAutomator)
device.findObject(By.res("com.example.app", "btn_add_to_cart"))
.click();
device.findObject(By.res("com.example.app", "cart_badge"))
.wait(Until.findObject(By.text("1")), 3000);
// Open cart
device.findObject(By.res("com.example.app", "nav_cart"))
.click();
// Proceed to checkout
device.findObject(By.res("com.example.app", "btn_checkout"))
.click();
// Verify that a payment app (e.g., Google Pay) is now in foreground
UiObject2 paymentApp = device.wait(Until.findObject(By.packageName("com.google.android.apps.nbu.paisa.user")), 8000);
assertNotNull(paymentApp);
// Optional: press back to return to your app
device.pressBack();
}
}
This test catches cases where the checkout intent is malformed or the target package name changes after an update.
---
Tooling and Frameworks Specific to Android
| Category | Tool / Library | Why It Helps for Cart Testing |
|---|---|---|
| Build & Dependency | Gradle with Kotlin DSL | Declarative configuration, easy to add testImplementation for JUnit, Mockito, Espresso |
| Testing Framework | JUnit5 + Truth / AssertJ | Readable assertions, extensible for custom matchers |
| Mocking | MockK or Mockito‑kotlin | Kotlin‑friendly, supports coroutines out‑of‑the‑box |
| Coroutines Test | kotlinx-coroutines-test | Deterministic testing of suspending functions |
| Dependency Injection | Hilt (or Dagger) | Enables swapping real repositories with fakes in tests |
| Database | Room + Room.inMemoryDatabaseBuilder | Fast, isolated persistence layer for integration tests |
| WorkManager Testing | ListenableFuture + TestListenableFuture | Validate background sync logic without waiting real time |
| UI Testing | Espresso (core + contrib) + UIAutomator | Precise view interactions; UIAutomator for cross‑app flows |
| Idling Resources | CountingIdlingResource or custom IdlingResource | Synchronize with network, LiveData, or WorkManager |
| Network Mocking | MockWebServer (OkHttp) | Enqueue responses, simulate latency, errors, timeouts |
| Accessibility | Accessibility Scanner + AccessibilityTest (Espresso) | Automated checks for contentDescription, contrast, tap targets |
| Security Scanning | MobSF, OWASP ZAP, or androidx.security:security-crypto tests | Detect clear‑text traffic, hard‑coded keys, logging of PII |
| CI/CD | GitHub Actions with android-emulator-runner or Firebase Test Lab | Run matrix on multiple API levels, screen sizes, locales |
| Performance Profiling | Android Studio Profiler, adb shell dumpsys gfxinfo | Detect jank during cart animations or heavy RecyclerView updates |
| Beta Distribution | Firebase App Distribution or Google Play Internal Test | Get real‑world feedback from power users before production release |
When setting up a new module for cart tests, a typical build.gradle snippet looks like:
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter:5.10.0"
testImplementation "org.mockito.kotlin:mockito-kotlin:5.0.0"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0"
androidTestImplementation "androidx.test:core:1.5.0"
androidTestImplementation "androidx.test.ext:junit:1.1.5"
androidTestImplementation "androidx.test.espresso:espresso-core:3.6.1"
androidTestImplementation "androidx.test.espresso:espresso-contrib:3.6.1"
androidTestImplementation "androidx.test.espresso:idling-resource:3.6.1"
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0"
androidTestImplementation "com.squareup.okhttp3:mockwebserver:4.12.0"
androidTestImplementation "androidx.room:room-testing:2.6.1"
androidTestImplementation "androidx.work:work-testing:2.9.0"
debugImplementation "com.google.android.apps.common.testing.accessibilityframework:accessibility-test-framework:1.2"
}
Remember to enable testOptions.unitTests.includeAndroidResources = true if you need to load XML resources in unit tests (rare for cart logic but useful for styling tests).
---
Concrete Examples: Code & Commands
Below are ready‑to‑copy snippets that illustrate frequent pain points and how to verify them in automation.
1. Simulating Network Latency with MockWebServer
// CartNetworkTest.kt
class CartNetworkTest {
private lateinit var mockWebServer: MockWebServer
private lateinit var cartRepository: CartRepository
@Before
fun setUp() {
mockWebServer = MockWebServer()
mockWebServer.start()
// Provide a Retrofit instance pointing to the mock server
val retrofit = Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.addConverterFactory(MoshiConverterFactory.create())
.build()
val api = retrofit.create(CartApi::class.java)
cartRepository = CartRepository(api) // assumes repository takes Api
}
@After
fun tearDown() = mockWebServer.shutdown()
@Test
fun `addItem_timeout_showsRetry`() = runTest {
// Enqueue a delayed response (5 s) then success
mockWebServer.enqueue(
MockResponse()
.setBody("""{"status":"ok"}""")
.setBodyDelay(5, TimeUnit.SECONDS)
)
mockWebServer.enqueue(
MockResponse()
.setResponseCode(200)
.setBody("""{"status":"ok"}""")
)
// Trigger add‑to‑cart (assume suspend function)
val job = launch { cartRepository.addItem("p1", 1) }
// Fast‑forward virtual time by 4 s → still waiting
advanceTimeBy(4000)
assertTrue(job.isActive) // not completed yet
// Fast‑forward another 2 s → response arrives
advanceByUntilIdle(2000)
assertFalse(job.isActive) // completed
// Verify UI would show retry button (pseudo‑assert)
// verify(viewModel).showRetry(true)
}
}
This test proves that your repository handles timeouts gracefully and does not create duplicate line items when the request is retried.
2. Checking for Duplicate Line Items Under Rapid Clicks
// RapidClickTest.kt
@OptIn(ExperimentalCoroutinesApi::class)
class RapidClickTest {
private lateinit var viewModel: CartViewModel
private lateinit var repository: FakeCartRepository
@Before
fun setUp() {
repository = FakeCartRepository()
viewModel = CartViewModel(repository)
}
@Test
fun `rapidAddSameItem_doesNotDuplicate`() = runTest {
val product = Product(id = "p99", name = "Gadget", price = Money(500))
// Simulate 10 rapid clicks
repeat(10) {
viewModel.addToCart(product, 1)
}
val cart = viewModel.cartFlow.first()
// Expect a single line item with quantity 10 (or max allowed)
assertEquals(1, cart.items.size)
assertEquals(10, cart.items.first().quantity)
}
}
If your UI uses a debouncing operator (e.g., debounce(300ms) on click events), this test validates that the debounce works.
3. Accessibility Espresso Test for Content Description
// CartAccessibilityTest.kt
@LargeTest
@RunWith(AndroidJUnit4::class)
class CartAccessibilityTest {
@get:Rule
val activityRule = ActivityScenarioRule(CartActivity::class)
@Test
fun eachCartRow_hasContentDescription() {
// Populate cart with two items via ViewModel (or direct repository)
// Assume helper function populateTestCart()
populateTestCart()
onView(withId(R.id.recycler_view))
.check {
// Use Matcher that asserts each child has non‑empty contentDesc
val recyclerView = it as RecyclerView
val adapter = recyclerView.adapter as? CartAdapter
assertNotNull(adapter)
for (i in 0 until adapter.itemCount) {
val viewHolder = recyclerView.findViewHolderForAdapterPosition(i)
assertNotNull(viewHolder)
val desc = viewHolder.itemView.contentDescription
assertTrue("Row $i missing contentDescription", desc.isNotBlank())
}
}
}
}
This test will fail fast if a developer forgets to set contentDescription on the remove button or the quantity selector.
4. Command‑Line Check for Clear‑Text Traffic
# Enable strict mode for clear‑text traffic in a debug build
adb shell setprop debug.strictmode.network true
# Run the app and exercise cart actions manually or via monkey
adb shell monkey -p com.example.app -v 500
# Look for warnings in logcat
adb logcat | grep "StrictMode"
If you see StrictMode policy violation; ~network~ lines, your app is making HTTP requests without TLS. Fix by enforcing android:usesCleartextTraffic="false" in the manifest or using NetworkSecurityConfig.
5. Generating a Regression Script with SUSA (Persona‑Driven)
After an autonomous run, SUSA can export the discovered flows as Appium scripts. Example command to pull the script:
susatest export --app cart-demo.apk --format appium-java --output regression/
The generated file contains a sequence of driver.findElement(By.id(...)).click() calls that replicate the exact paths the autonomous agent explored, including edge cases like rapid‑add clicks and coupon‑apply failures. You can then commit this to your version‑control repo and run it in your CI pipeline as a safety net.
---
Edge Cases That Only Appear in Production
Even the most thorough test matrix can miss issues that emerge under specific runtime conditions. Below are real‑world scenarios that have caused cart bugs in the wild, along with detection strategies.
| Scenario | Why It’s Hard to Catch in Lab | Detection Technique |
|---|---|---|
| Battery‑saver throttles WorkManager | Emulators and dev devices often ignore battery optimizations; on low‑end phones, the OS may defer sync for minutes. | Use adb shell cmd jobscheduler run to force execution, or run the app with adb shell am set-debug-app -w com.example.app and observe delays. |
| Locale‑specific number formatting | A test device set to en-US may parse "1.234,56" incorrectly when the app runs in de-DE. |
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